SearXNG 셀프 호스팅: Docker로 나만의 검색 엔진 만들기
Docker Compose를 사용하여 SearXNG를 VPS에 직접 구축하는 방법을 설명합니다. settings.yml 설정, Nginx TLS 적용, JSON 검색 API 활용법을 포함하며 개인용 검색 엔진 운영을 위한 필수 가이드를 제공합니다.
구축할 대상
SearXNG를 직접 호스팅하면 본인의 서버에서 실행되는 개인용 검색 엔진을 가질 수 있습니다. SearXNG는 메타 검색 엔진입니다. 사용자의 질의를 받아 Google, Bing, DuckDuckGo, Wikipedia와 같은 다른 엔진에 전달한 뒤, 결과를 하나로 합쳐 보여줍니다. 프로필이 생성되지 않고 추적 쿠키도 설정되지 않습니다. 질의를 기록하는 기기는 오직 사용자의 서버뿐이기 때문입니다. 단순히 Searx라고 불리는 구형 가이드를 찾았다면, 이는 현재 프로젝트의 원형이 된 프로젝트입니다. 해당 프로젝트는 2023년 이후 커밋이 없으므로 가이드를 따르기 전에 두 프로젝트의 상태를 확인하십시오.
스택은 간소합니다. 컨테이너 2개, 설정 파일 1개, 리버스 프록시 1개로 구성됩니다. 모든 셀프 호스팅 서비스가 그렇지는 않지만, 이 구성은 소규모 VPS에서도 원활하게 작동합니다. PhotoPrism과 Immich 비교에서 다룬 사진 라이브러리들은 웹 앱이 아닌 인덱서가 최소 RAM 사용량을 결정하는 것과 대조적입니다. 가장 중요한 결정 사항은 인스턴스를 비공개로 운영할지, 공개로 운영할지입니다. 비공개는 본인과 본인의 스크립트만 접근하는 것을 의미하며, 공개는 인터넷상의 누구나 검색할 수 있음을 의미합니다. 이 선택에 따라 보안 설정이 달라지므로 명령어를 입력하기 전에 결정해야 합니다. 기본값은 비공개입니다.
SearXNG를 운영해야 하는 두 번째 이유가 있습니다. SearXNG 인스턴스는 JSON을 지원합니다. 따라서 직접 작성한 스크립트나 AI 에이전트에서 API 키, 쿼리당 비용, 할당량 제한 없이 본인 소유의 검색 API를 사용할 수 있습니다.
Docker Compose를 이용한 SearXNG 설치
이 프로젝트는 컨테이너 이미지와 Compose 파일을 제공합니다. Docker Engine과 Compose 플러그인이 설치된 최신 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는 속도 제한 및 단기 상태 저장을 위한 인메모리 데이터 저장소입니다. 이 설정은 컨테이너 내부의 /etc/searxng/에 ./core-config/을 마운트하므로, 모든 설정 파일은 호스트의 해당 디렉터리에 저장됩니다.
이제 .env를 편집하십시오. 제공된 예제 파일의 모든 줄은 주석 처리되어 있으며, 이 때문에 컨테이너는 모든 주소의 8080 포트에서 시작됩니다. 다음 세 가지 항목의 주석을 해제하고 설정하십시오.
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로 변경하여, 컨테이너가 루프백 주소에서만 응답하게 하고 외부 인터넷에서 직접 접근할 수 없도록 합니다. 이 단계를 건너뛰면 Docker가 게시한 포트가 방화벽 규칙보다 우선 적용되므로 컨테이너가 시작되는 즉시 외부로 노출됩니다. 이 위험 요소에 대한 자세한 내용은 게시된 Docker 포트가 ufw를 우회하는 현상을 반드시 읽어보시기 바랍니다.
SEARXNG_VERSION=latest는 학습 단계에서는 적절합니다. 하지만 운영 중인 서버라면 태그를 고정하십시오. 2026년 7월 기준으로 릴리스 태그는 날짜 기반의 2026.3.25-541c6c3cb 형식을 사용합니다. 태그를 고정하면 레지스트리의 변경 사항에 따라 자동으로 업데이트되지 않으며, 관리자가 직접 결정할 때 업데이트를 수행할 수 있습니다. 이러한 관리 방식은 서버에서 장기간 운영되는 모든 서비스에 유효합니다. 자체 호스팅 RustDesk 릴레이 설정 시에도 이미지 태그를 고정하는 이유가 바로 이것입니다. 원격 접속 서비스가 예고 없이 업데이트되면 가장 곤란한 순간에 서비스 장애가 발생할 수 있기 때문입니다.
settings.yml: 중요한 설정 항목
첫 실행 전에 core-config/settings.yml을 생성하십시오. use_default_settings: true를 사용하면 SearXNG가 자체적으로 제공하는 기본값을 먼저 불러온 뒤 사용자가 작성한 키만 적용하므로, 설정 파일의 길이를 짧게 유지할 수 있고 새로운 옵션이 추가되는 업데이트 시에도 설정이 유지됩니다.
비밀 키 값은 파일에 직접 입력해야 하므로 먼저 생성하십시오.
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은 세션 및 토큰 데이터를 서명하는 데 사용됩니다. 기본값은 ultrasecretkey라는 문자열로 설정되어 있는데, 이를 그대로 두면 해당 기본값을 아는 누구나 토큰을 위조할 수 있습니다. 한 번 변경한 뒤에는 그대로 두십시오. 나중에 변경하면 저장된 모든 사용자 설정이 초기화됩니다.
base_url는 반드시 공개된 HTTPS 주소여야 하며, 끝에 슬래시(/)를 포함해야 합니다. SearXNG가 렌더링하는 링크에 이 주소가 기록됩니다. 이를 localhost로 두면 원격 브라우저에서 "다음 페이지" 링크를 클릭했을 때 사용자의 로컬 컴퓨터를 가리키게 되어 연결에 실패합니다.
formats은 웹 엔드포인트가 생성할 출력 형식을 결정합니다. json은 기본 목록에 포함되어 있지 않으므로, 이를 추가하지 않으면 JSON 요청 시 403 오류가 발생합니다. image_proxy: true은 결과 썸네일을 서버를 거쳐 전달하게 하므로, 이미지를 호스팅하는 사이트가 방문자의 IP 주소를 확인할 수 없게 합니다.
valkey.url는 valkey 호스트 이름을 사용합니다. 이는 Compose 파일 내의 서비스 이름이며, Compose는 두 컨테이너를 동일한 네트워크에 배치하여 서비스 이름으로 서로를 식별할 수 있게 합니다. 이를 localhost로 설정하면 제한기가 작동하지 않습니다. core 컨테이너 내부에서 localhost는 해당 컨테이너 자신을 의미하기 때문입니다.
비밀 키는 일반 텍스트 파일에 저장되므로, 파일 자체보다는 해당 디렉터리의 권한을 보호하십시오. chmod 750 /opt/searxng을 사용하면 다른 호스트 사용자의 접근을 차단할 수 있습니다. core-config/settings.yml를 600 모드로 너무 엄격하게 설정하지 마십시오. 컨테이너는 권한이 없는 별도의 사용자로 실행되므로, 파일을 읽을 수 없으면 SearXNG가 시작되지 않습니다.
스택을 시작하고 상태를 확인하십시오.
cd /opt/searxng
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080/docker compose ps을 실행하면 두 컨테이너 모두 running 상태여야 합니다. curl은 HTTP/1.1 200 OK를 응답해야 합니다. 아무런 응답이 없다면 docker compose logs core을 확인하십시오. settings.yml의 YAML 구문 오류는 해당 파일에서 줄 번호와 함께 파싱 오류로 표시됩니다.
Nginx를 이용한 TLS 적용
컨테이너는 루프백 인터페이스에서만 대기하므로 Nginx를 통해 외부 접근을 허용하고 전송 계층 보안(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은 설정을 다시 불러오기 전에 syntax is ok와 test is successful를 출력합니다. Certbot은 동일한 파일을 다시 작성하여 인증서와 함께 443 포트에서 대기하도록 설정하고 80 포트에서 리다이렉트를 추가합니다. search.example.com에 대한 DNS 레코드는 이미 이 서버를 가리키고 있어야 합니다. 인증 기관은 HTTP를 통해 파일을 가져오는 방식으로 소유권을 확인하기 때문입니다. 갱신을 포함한 전체 과정은 Ubuntu 24.04용 Certbot 및 Nginx 가이드에서 확인할 수 있습니다.
두 개의 전달 헤더는 단순한 장식이 아닙니다. X-Forwarded-For과 X-Real-IP이 없으면 SearXNG에 도착하는 모든 요청이 프록시 주소를 가지게 됩니다. 이 경우 속도 제한(rate limiter) 기능은 모든 트래픽이 단일 클라이언트에서 발생하는 것으로 간주하여 방문자를 구분할 수 없게 됩니다.
스크립트와 에이전트가 JSON 검색 API를 필요로 하는 이유
json를 formats에서 사용하면, 페이지를 렌더링하는 동일한 엔드포인트가 구조화된 데이터를 반환합니다.
curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
| jq -r '.results[0:5][] | .url'결과로 results 배열을 포함한 객체를 받게 되며, 각 항목은 url, title, content와 이를 제공한 엔진, 그리고 answers, infoboxes 및 suggestions 정보를 담고 있습니다. 이는 요약기, 링크 검사기, 또는 연구 루프에 데이터를 공급하기에 충분합니다. 이러한 결과를 언어 모델에 전달하는 것은 생각보다 큰 단계입니다. 검색 결과는 그 자체로 명령을 포함할 수 있는 신뢰할 수 없는 텍스트이기 때문이며, SearXNG 인스턴스를 AI 에이전트에 연결하기에서 이 문제를 자세히 다룹니다.
이는 에이전트 형태를 띤 모든 것에 중요합니다. 언어 모델은 학습 데이터의 한계가 있으므로 현재에 관한 질문에 답하려면 실시간 검색이 필요합니다. 반면 상용 검색 API는 쿼리당 비용을 청구하며 엄격한 속도 제한을 둡니다. 로컬 인스턴스는 이미 비용을 지불 중인 서버의 컨테이너 하나만 사용하며, 쿼리가 외부로 나가지 않습니다. 모델에 도구를 연결하는 경우, 동일한 논리가 VPS에서 MCP 서버 실행하기로 이어지며, 검색 도구는 보통 사람들이 가장 먼저 추가하는 도구입니다.
API 사용을 위한 두 가지 규칙이 있습니다. 인스턴스를 비공개로 유지하십시오. API 측을 루프백 주소나 사설 네트워크에 바인딩하여 본인의 호스트만 접근할 수 있도록 제한해야 합니다. 또한 쿼리를 부드럽게 수행하십시오. SearXNG는 귀하의 요청을 실제 검색 엔진으로 전달하므로, 초당 100개의 쿼리를 실행하는 스크립트는 Google이 귀하의 서버를 차단하도록 유도하는 것과 같습니다.
리미터와 공개 인스턴스 운영 시 변경 사항
리미터는 SearXNG의 봇 방어 기능입니다. 요청 헤더, 주소, 요청 속도를 감시하여 자동화된 것으로 보이는 트래픽을 차단합니다. 이 상태를 유지하기 위해 Valkey가 필요하며, 이것이 Compose 파일에 포함된 이유입니다.
개인용 인스턴스라면 limiter: false을 유지하십시오. 사용자의 스크립트는 정의상 자동화된 트래픽이므로, 리미터는 인스턴스를 구축한 목적인 JSON 호출을 정확히 차단하게 됩니다. 대신 접근 제어는 리버스 프록시의 역할입니다. nginx location 설정의 allow 및 deny 쌍, HTTP 기본 인증, 또는 다른 서버만 허용하는 방화벽을 사용하십시오. 네트워크를 이동하는 노트북에서 개인 인스턴스에 접근해야 한다면, v3 onion 주소를 앞에 두는 것이 네 번째 선택지입니다. Tor는 인터넷에 새로운 포트를 노출하지 않고도 동일한 루프백 포트로 연결되기 때문입니다.
인스턴스를 다른 사람들에게 공개한다면 두 스위치를 모두 켜십시오.
server:
limiter: true
public_instance: true더 세밀한 제어는 core-config/limiter.toml에서 수행하며, 컨테이너는 이를 /etc/searxng/limiter.toml에서 읽습니다. 변경하려는 키만 작성하십시오. 프록시 뒤에 있다면 프록시를 선언해야 합니다. 그렇지 않으면 리미터가 nginx 주소를 하나의 악성 클라이언트로 취급합니다.
[botdetection]
trusted_proxies = [
'127.0.0.0/8',
'::1',
]
[botdetection.ip_limit]
link_token = truelink_token = true는 실제 브라우저 세션만 가져올 수 있는 토큰을 SearXNG가 발행하게 하여, 대부분의 단순 스크래퍼를 차단합니다. 공개 인스턴스는 며칠 내로 스크래퍼의 공격을 받게 될 것입니다. 엔진 오류도 예상하십시오. 전달하는 트래픽이 많을수록 업스트림 엔진이 서버 주소로 CAPTCHA를 반환하는 시점이 빨라지기 때문입니다. 공개 SearXNG 인스턴스는 지속적인 관리가 필요한 작업입니다. 개인용 인스턴스는 그렇지 않으며, 이것이 2026년에 자가 호스팅할 가치가 있는 것들 목록 대부분에 포함되는 이유입니다. 해당 목록의 모든 항목이 인프라인 것은 아닙니다. Jellyfin 라이브러리를 90년대 대여점처럼 구성하기는 동일한 nginx 블록 뒤에 있는 동일한 컨테이너를 사용하며, 작업 흐름보다는 저녁 시간을 위한 용도입니다.
검색 결과가 나오지 않는 이유
인스턴스에서 /stats를 엽니다. 이 페이지에는 각 엔진의 오류율과 응답 시간이 나열되어 있으며, 검색 결과가 부족하다고 느껴질 때 가장 먼저 확인해야 할 곳입니다.
"Access denied" 또는 "CAPTCHA" 오류가 표시되는 엔진은 서버 주소를 차단한 상태다. 데이터센터 대역의 주소에서 이런 문제가 흔하다. 검색 엔진이 해당 주소를 스크레이퍼에 속한 것으로 간주하기 때문이다. SearXNG는 실패한 엔진을 계속 재시도하지 않고 일정 기간 일시 중지한다. 따라서 차단된 엔진 하나가 결과에서 조용히 제외된다. settings.yml에서 해당 엔진을 비활성화하거나 이 손실을 감수한다. 다만 선택지는 이 두 가지뿐이 아니다. 일부 CAPTCHA 차단은 재시작 후에도 유지되는 해결 방법이 있다. 나머지 엔진은 계속 응답한다. 429는 판단하기 어려운 경우다. 자체 rate limiter에서 발생했을 수도 있고, upstream 엔진이 서버의 요청을 거부해서 발생했을 수도 있기 때문이다. 설정을 변경하기 전에 로그 행에서 두 경우 중 어느 쪽인지 확인할 수 있다.
모든 엔진이 동시에 실패한다면 컨테이너에 정상적인 아웃바운드 이름 해석 기능이 없거나 인터넷으로 향하는 경로가 없는 상태입니다. 컨테이너 내부에서 이를 테스트하십시오.
docker compose exec core wget -qO- https://duckduckgo.com > /dev/null && echo ok이 검사가 실패하기 시작할 때 이를 알려주는 기능은 상자(box) 내부에 없습니다. 따라서 cron에서 실행하고 실패 시 직접 운영하는 ntfy 서버를 통해 휴대폰으로 알림을 푸시하도록 설정하십시오. 검색 결과가 줄어든 것을 알아챌 때까지 기다릴 필요가 없습니다.
FAQ
SearXNG를 사용하면 검색이 익명으로 처리됩니까?
SearXNG는 사용자의 브라우저가 아닌 서버가 직접 검색 엔진에 요청을 보내므로, 검색 엔진으로부터 사용자의 신원을 숨깁니다. 하지만 서버가 검색 쿼리를 확인하는 것까지 막지는 않으며, 검색 엔진으로부터 서버의 IP 주소를 숨기지도 않습니다. 단일 사용자 인스턴스를 운영하는 경우 해당 IP에서 발생하는 모든 트래픽은 사용자의 것이므로, IP 주소 자체가 식별자가 됩니다. 브라우저와 인스턴스 사이의 트래픽은 TLS 인증서로 보호됩니다. ISP, 공개 인스턴스 운영자, 검색 엔진 사이에서 사용자의 정보가 어떻게 처리되는지에 대한 자세한 내용은 SearXNG가 실제로 숨기는 정보에서 확인할 수 있습니다.
JSON 요청 시 403 Forbidden 오류가 발생하는 이유는 무엇입니까?
두 가지 원인이 있으며 모두 설정과 관련이 있습니다. 첫 번째는 settings.yml 내 search: 항목의 formats 목록에 json이 누락된 경우(기본 상태)이며, 두 번째는 제한기(limiter)가 활성화되어 사용자의 스크립트를 봇으로 분류한 경우입니다. 먼저 형식을 추가하고 docker compose restart core로 재시작한 뒤 다시 시도하십시오. 그래도 실패한다면 limiter: false를 설정하고 리버스 프록시에서 접근을 제어하십시오.
제한기를 사용하지 않아도 Valkey 컨테이너가 필요합니까?
계속 실행해 두십시오. SearXNG는 Valkey 없이도 작동하지만, 나중에 제한기를 켜려면 반드시 필요하며 다른 단기 상태 정보도 저장합니다. 컨테이너 용량이 작고 캐시 데이터만 저장하므로, 삭제하더라도 얻는 이점은 거의 없으며 나중에 기능을 사용할 수 없게 됩니다.
SearXNG는 어떻게 업데이트합니까?
/opt/searxng 디렉터리에서 docker compose pull을 실행한 후 docker compose up -d를 실행하십시오. Docker Compose는 이미지가 변경된 컨테이너만 다시 생성하며 core-config/ 디렉터리는 건드리지 않으므로 settings.yml은 그대로 유지됩니다. use_default_settings: true은 사용자의 키를 기본 설정값과 병합하므로, 업스트림에서 추가된 옵션은 파일이 깨지지 않고 적절한 기본값으로 적용됩니다.
여러 사람이 하나의 인스턴스를 공유할 수 있습니까?
네, 가능합니다. 이 경우 제한기를 켜고 public_instance: true를 설정하십시오. 환경 설정은 각 방문자의 브라우저에 저장되므로 별도로 관리할 계정이 필요하지 않습니다. 인스턴스를 공개한 후 일주일 동안은 /stats을 모니터링하십시오. 검색 결과가 누락되기 훨씬 전부터 업스트림 검색 엔진이 서버의 요청을 거부하기 시작할 수 있기 때문입니다.