SearXNG 자체 호스팅과 비공개 검색 설정
Docker Compose로 SearXNG를 VPS에 설치하고 settings.yml, limiter, nginx TLS를 구성합니다. JSON 검색 API를 스크립트에서 호출하는 방법과 비공개 기본값을 설명합니다.
구축하는 항목
SearXNG를 자체 호스팅하면 자신의 서버에서 실행되는 비공개 검색 엔진을 사용할 수 있습니다. SearXNG는 메타검색 엔진입니다. 입력한 쿼리를 Google, Bing, DuckDuckGo, Wikipedia 같은 다른 엔진에 전달한 다음, 반환된 결과를 하나의 결과 페이지로 통합합니다. 프로필을 만들지 않고 추적 쿠키도 설정하지 않습니다. 쿼리를 보관하는 컴퓨터는 자신의 컴퓨터뿐이기 때문입니다.
구성은 간단합니다. 컨테이너 2개, 설정 파일 1개, reverse proxy 1개로 구성됩니다. 실제로 결정해야 할 사항은 인스턴스를 비공개로 운영할지, 공개로 운영할지입니다. 비공개 인스턴스는 자신과 자신의 스크립트만 접근할 수 있습니다. 공개 인스턴스는 인터넷의 누구나 쿼리를 보낼 수 있습니다. 이 선택에 따라 보안 설정이 달라지므로, 명령을 입력하기 전에 결정해야 합니다. 기본값은 비공개입니다.
인스턴스를 직접 운영하는 이유는 하나 더 있습니다. SearXNG 인스턴스는 JSON을 사용하므로, 작성한 스크립트나 AI 에이전트가 자신이 소유한 검색 API를 사용할 수 있습니다. 키가 필요하지 않고, 쿼리별 요금이나 할당량 관련 메일도 없습니다.
Docker Compose로 SearXNG 설치
프로젝트는 컨테이너 이미지와 Compose 파일을 제공합니다. Docker Engine과 Compose plugin이 이미 설치된 새 Ubuntu 24.04 서버에 두 항목을 모두 가져옵니다. Docker가 처음이라면 VPS에서 Docker Compose 기본 사항부터 확인한 후 돌아옵니다.
sudo install -d -o "$USER" -g "$USER" -m 750 /opt/searxng
cd /opt/searxng
mkdir -p core-config
curl -fsSL \
-O https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml \
-O https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example
cp -i .env.example .envCompose 파일은 두 서비스를 정의합니다. core은 SearXNG 자체이고, valkey는 속도 제한과 단기 상태 저장에 사용하는 메모리 내 데이터 저장소입니다. 호스트의 ./core-config/을 컨테이너 내부의 /etc/searxng/에 마운트하므로, 구성하는 모든 항목은 호스트의 해당 디렉터리 하나에 저장됩니다.
이제 .env를 편집합니다. 제공된 예제의 모든 줄이 주석 처리되어 있습니다. 따라서 컨테이너는 모든 주소에서 포트 8080으로 시작합니다. 다음 3개 항목의 주석을 제거하고 값을 설정합니다.
SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080SEARXNG_HOST=127.0.0.1이 중요합니다. 이 설정은 게시 포트를 [::]:8080:8080 대신 127.0.0.1:8080:8080로 지정합니다. 따라서 컨테이너는 loopback 주소에서만 응답하고 인터넷에서 직접 접근할 수 없습니다. 이 설정을 생략하면 컨테이너가 시작하는 즉시 외부에 노출됩니다. 게시된 Docker 포트가 firewall 규칙보다 앞에 삽입되기 때문입니다. 이 문제에 대한 자세한 설명은 게시된 Docker 포트가 ufw를 우회하는 방식에서 확인합니다.
SEARXNG_VERSION=latest는 학습하는 동안 사용해도 됩니다. 중요한 서버에서는 tag를 고정합니다. 2026년 7월 기준으로 release tag는 날짜 기반이며 2026.3.25-541c6c3cb과 같은 형식입니다. 따라서 registry 변경에 따라 배포가 자동으로 업그레이드되지 않고, 원하는 시점에 업그레이드할 수 있습니다.
settings.yml: 중요한 항목
첫 번째 시작 전에 core-config/settings.yml을 만듭니다. use_default_settings: true은 SearXNG이 자체 제공 기본값을 불러온 다음 사용자가 작성한 키만 적용하도록 합니다. 따라서 파일을 짧게 유지할 수 있고 새 옵션이 추가되는 업그레이드도 견딜 수 있습니다.
먼저 secret을 생성합니다. 값이 파일에 바로 들어가기 때문입니다.
openssl rand -hex 32use_default_settings: true
general:
instance_name: "search.example.com"
server:
base_url: "https://search.example.com/"
secret_key: "paste-the-openssl-output-here"
limiter: false
public_instance: false
image_proxy: true
valkey:
url: valkey://valkey:6379/0
search:
safe_search: 0
autocomplete: "duckduckgo"
formats:
- html
- jsonsecret_key은 세션 및 token 데이터를 서명합니다. 제공되는 기본값은 리터럴 문자열 ultrasecretkey이며, 그대로 두면 이 기본값을 아는 누구나 해당 token을 위조할 수 있습니다. 한 번 교체한 뒤에는 그대로 둡니다. 나중에 변경하면 저장된 모든 preference가 삭제됩니다.
base_url는 trailing slash를 포함한 public HTTPS 주소여야 합니다. SearXNG은 렌더링하는 링크에 이 주소를 기록합니다. localhost를 가리키도록 두면 remote browser의 "next page" 링크가 독자 컴퓨터 자체를 가리켜 실패합니다.
formats은 web endpoint가 생성할 output type을 결정합니다. json은 기본 목록에 없으므로 추가하기 전에는 JSON 요청이 403을 반환합니다. image_proxy: true은 result thumbnail을 서버를 통해 전달합니다. 따라서 해당 이미지를 호스팅하는 사이트는 방문자의 주소를 확인할 수 없습니다.
valkey.url은 hostname valkey을 사용합니다. Compose 파일에서 해당 값이 service name이며, Compose는 두 container를 service name을 resolve할 수 있는 하나의 network에 배치하기 때문입니다. localhost을 가리키면 limiter가 실패합니다. core container 내부에서 localhost는 해당 container를 의미하기 때문입니다.
secret은 일반 파일에 저장되므로 파일 자체보다 파일이 있는 directory를 보호합니다. chmod 750 /opt/searxng은 다른 host user의 접근을 차단합니다. core-config/settings.yml를 mode 600로 더 제한하지 마십시오. container는 자체 unprivileged user로 실행되며, 이 user가 읽을 수 없는 파일이 있으면 SearXNG이 아예 시작되지 않습니다.
stack을 시작하고 확인합니다.
cd /opt/searxng
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080/docker compose ps에는 두 container가 모두 running 상태로 표시되어야 합니다. curl은 HTTP/1.1 200 OK에 응답해야 합니다. 아무 응답이 없으면 docker compose logs core을 확인합니다. settings.yml의 YAML 오류가 해당 위치에 줄 번호를 표시하는 parse error로 나타나기 때문입니다.
nginx 뒤에 TLS와 함께 배치
컨테이너는 loopback에서만 수신하므로 nginx가 외부에서 접근할 수 있게 합니다. 또한 nginx가 transport layer security(TLS)를 추가합니다. /etc/nginx/sites-available/searxng를 작성합니다.
server {
listen 80;
server_name search.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo ln -s /etc/nginx/sites-available/searxng /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d search.example.comnginx -t는 reload하기 전에 syntax is ok 및 test is successful를 출력합니다. Certbot은 동일한 파일을 다시 작성하여 인증서와 함께 443에서 수신하도록 설정하고, port 80에서 redirect를 추가합니다. 인증 기관은 HTTP를 통해 파일을 가져와 소유권을 확인하므로 search.example.com의 DNS 레코드는 이미 이 서버를 가리켜야 합니다. 갱신을 포함한 전체 절차는 Ubuntu 24.04용 Certbot 및 nginx 가이드에 설명되어 있습니다.
두 forwarding header는 장식이 아닙니다. X-Forwarded-For 및 X-Real-IP이 없으면 SearXNG에 도착하는 모든 요청에 proxy 주소가 포함됩니다. 따라서 rate limiter는 모든 traffic을 하나의 client가 생성한 것으로 판단하며 방문자를 서로 구분할 수 없습니다.
스크립트와 에이전트가 JSON 검색 API를 사용하는 이유
formats에 json가 있으면 페이지를 렌더링하는 동일한 endpoint가 구조화된 데이터를 반환합니다.
curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
| jq -r '.results[0:5][] | .url'url, title, content 및 데이터를 제공한 engine이 각 항목에 포함된 results array가 반환됩니다. 여기에 answers, infoboxes 및 suggestions도 포함됩니다. 이 데이터만으로도 summariser, link checker 또는 research loop에 전달할 수 있습니다.
이는 agent 형태의 모든 작업에 중요합니다. language model에는 training cutoff가 있으므로 현재 상황에 관한 질문에 답하려면 실시간 검색이 필요합니다. 또한 상용 search API는 query마다 요금을 부과하고 엄격하게 rate limit을 적용합니다. 이미 비용을 지불하고 있는 server에서 local instance를 container 하나로 실행하면 되며, query는 외부로 전송되지 않습니다. model에 tool을 연결하는 경우에도 같은 이유로 VPS에서 MCP server 실행을 고려하게 됩니다. search tool은 일반적으로 사람들이 가장 먼저 추가하는 tool입니다.
API 사용에는 2가지 규칙이 있습니다. 먼저 instance를 비공개로 유지합니다. API 측을 loopback address 또는 private network에 bind하고, 자체 host만 접근하도록 합니다. 그런 다음 API를 과도하게 query하지 않습니다. SearXNG는 요청을 실제 search engine으로 전달하므로, script가 초당 100개의 query를 실행하면 Google이 server를 차단하도록 유도하는 셈입니다.
limiter와 public instance에서 달라지는 사항
limiter는 SearXNG의 bot 방어 기능입니다. 요청 헤더, 주소 및 요청 속도를 감시하고 자동화된 것으로 보이는 traffic을 차단합니다. 이 상태를 저장하려면 Valkey가 필요하므로 Compose 파일에 포함되어 있습니다.
private instance에서는 limiter: false를 유지합니다. 자체 스크립트는 정의상 자동화된 traffic이므로, limiter는 해당 instance를 사용하기 위해 만든 JSON 호출을 바로 차단합니다. 대신 access control은 reverse proxy의 역할입니다. nginx location의 allow 및 deny pair, HTTP basic authentication 또는 다른 서버만 허용하는 firewall을 사용합니다.
다른 사용자가 instance를 이용하도록 공개하는 경우에는 두 switch를 모두 켭니다.
server:
limiter: true
public_instance: true더 세부적으로 제어하려면 core-config/limiter.toml를 사용합니다. container는 /etc/searxng/limiter.toml에서 이 파일을 읽습니다. 변경할 key만 작성합니다. proxy 뒤에서 실행하는 경우 proxy를 선언해야 합니다. 선언하지 않으면 limiter가 nginx 주소를 공격적인 단일 client의 주소로 처리합니다.
[botdetection]
trusted_proxies = [
'127.0.0.0/8',
'::1',
]
[botdetection.ip_limit]
link_token = truelink_token = true를 사용하면 SearXNG가 실제 browser session만 가져올 수 있는 token을 발급하므로, 단순한 scraper 대부분을 차단할 수 있습니다. public instance는 며칠 안에 scraper의 대상이 될 수 있습니다. engine 오류도 예상해야 합니다. 전달하는 traffic이 많을수록 upstream engine이 서버 주소에 CAPTCHA를 반환하기 시작하는 시점이 빨라지기 때문입니다. public SearXNG instance를 운영하는 일은 지속적인 관리 작업입니다. private instance에는 이러한 작업이 필요하지 않습니다. 따라서 private instance는 2026년에 self-hosting할 가치가 있는 항목의 짧은 목록에 대부분 포함됩니다.
검색 결과가 반환되지 않는 이유
인스턴스에서 /stats를 엽니다. 이 페이지에는 모든 엔진의 오류율과 응답 시간이 표시됩니다. 결과가 부족해 보일 때 가장 먼저 확인해야 하는 곳입니다.
"Access denied" 또는 "CAPTCHA" 오류가 표시되는 엔진은 서버 주소를 차단한 상태입니다. 검색 엔진은 데이터 센터 대역의 주소가 스크레이퍼에 속한다고 간주하므로, 이러한 주소에서는 이 문제가 자주 발생합니다. 그러면 SearXNG는 실패한 엔진을 다시 시도하지 않고 일정 기간 중지합니다. 따라서 차단된 엔진 하나가 결과에서 조용히 제외됩니다. settings.yml에서 해당 엔진을 비활성화하거나, 이 손실을 감수합니다. 나머지 엔진은 계속 응답합니다.
모든 엔진이 동시에 실패하면 컨테이너에서 외부 이름 확인이 작동하지 않거나 인터넷으로 연결되는 경로가 없는 것입니다. 컨테이너 내부에서 이를 테스트합니다.
docker compose exec core wget -qO- https://duckduckgo.com > /dev/null && echo okFAQ
SearXNG는 검색을 익명으로 만들어 줍니까?
SearXNG가 요청하는 검색 엔진에는 사용자의 브라우저가 아니라 사용자의 서버가 요청을 보낸 것으로 표시되므로, 해당 엔진에서 사용자의 신원을 숨깁니다. 그러나 사용자의 서버에서는 검색어가 숨겨지지 않으며, 검색 엔진에서 사용자의 서버도 숨겨지지 않습니다. 단일 사용자 인스턴스에서는 해당 주소에서 발생하는 모든 트래픽이 사용자의 것이므로 주소 자체가 식별자가 됩니다. 사용자의 브라우저와 인스턴스 사이의 트래픽은 TLS 인증서로 보호됩니다.
JSON 요청에서 403 Forbidden이 반환되는 이유는 무엇입니까?
원인은 2가지이며, 모두 구성 문제입니다. 기본 상태에서는 settings.yml의 search: 아래 formats 목록에 json이 없거나, limiter가 활성화되어 스크립트를 봇으로 분류한 경우입니다. 먼저 형식을 추가하고 docker compose restart core로 다시 시작한 다음 다시 시도합니다. 그래도 실패하면 limiter: false를 설정하고 reverse proxy에서 액세스를 제어합니다.
limiter를 끈 상태에서도 Valkey 컨테이너가 필요합니까?
계속 실행 상태로 둡니다. SearXNG는 Valkey 없이도 작동하지만, Valkey가 없으면 나중에 limiter를 활성화할 수 없으며 다른 단기 상태도 저장합니다. 컨테이너는 작고 캐시된 데이터만 저장하므로, 제거해도 절약되는 양은 매우 적고 해당 기능을 사용할 수 없게 됩니다.
SearXNG를 어떻게 업데이트합니까?
/opt/searxng에서 docker compose pull을 실행한 다음 docker compose up -d를 실행합니다. Compose는 이미지가 변경된 컨테이너를 다시 만들고 core-config/ 디렉터리는 그대로 두므로 settings.yml이 유지됩니다. use_default_settings: true은 제공된 기본값에 사용자의 키를 병합하므로, upstream에서 추가된 옵션이 파일을 손상시키지 않고 적절한 값으로 적용됩니다.
여러 사람이 하나의 인스턴스를 공유할 수 있습니까?
가능합니다. 여러 사람이 공유하는 경우 limiter를 활성화하고 public_instance: true를 설정합니다. 기본 설정은 각 방문자의 브라우저에 저장되므로 관리할 계정이 없습니다. 인스턴스를 공개한 후 1주일 동안 /stats을 모니터링합니다. upstream 검색 엔진은 결과가 누락된 것을 사용자가 알아차리기 훨씬 전에 사용자의 서버 요청을 거부하기 시작합니다.