SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-10-19

SearXNG 셀프 호스팅: Docker Compose 구축 가이드

Docker Compose를 사용하여 개인용 SearXNG 검색 엔진을 구축하는 방법을 설명합니다. settings.yml 설정, Nginx TLS 적용, 그리고 스크립트에서 활용 가능한 JSON API 연동까지 소규모 VPS 환경에 최적화된 설치 과정을 단계별로 안내합니다.

구축하려는 것

SearXNG를 직접 호스팅하면 귀하의 서버에서 실행되는 개인용 검색 엔진을 갖게 됩니다. SearXNG는 메타 검색 엔진입니다. 귀하의 쿼리를 받아 Google, Bing, DuckDuckGo, Wikipedia와 같은 다른 엔진에 전달한 뒤, 돌아오는 결과를 하나의 결과 페이지로 통합합니다. 귀하의 쿼리를 보관하는 기기는 귀하의 서버뿐이므로 프로필이 생성되지 않으며 추적 쿠키도 설정되지 않습니다. 단순히 Searx라고 불리는 프로젝트에 대한 이전 가이드를 찾으셨다면, 이 프로젝트가 바로 그곳에서 포크된 것입니다. 해당 프로젝트는 2023년 9월 자체 저장소에서 더 이상 유지보수되지 않는다고 발표한 이후 관리되지 않고 있으며, 2026년 5월에 문서 관련 메모로 커밋이 하나 있었으나 상황은 변하지 않았습니다. 따라서 가이드를 따르기 전에 두 프로젝트의 상태를 확인하십시오.

스택은 작습니다. 컨테이너 2개, 설정 파일 1개, 리버스 프록시 1개로 구성됩니다. 모든 셀프 호스팅 서비스가 그렇지는 않지만, 이 서비스는 작은 VPS에서도 원활하게 작동합니다. PhotoPrism과 Immich 비교에서 다룬 사진 라이브러리들은 웹 앱이 아닌 인덱서가 RAM 사용량의 하한선을 결정합니다. 중요한 결정 사항은 인스턴스를 비공개로 운영할지, 아니면 공개로 운영할지입니다. 비공개는 귀하와 귀하의 스크립트만 접근하는 것을 의미하며, 공개는 인터넷상의 누구나 쿼리를 보낼 수 있음을 의미합니다. 이 선택에 따라 보안 설정이 달라지므로 명령어를 입력하기 전에 결정하십시오. 기본값은 비공개입니다.

이를 운영해야 하는 두 번째 이유가 있습니다. 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 .env

Compose 파일은 두 개의 서비스를 정의합니다. core은 SearXNG 본체이며, valkey는 속도 제한 및 단기 상태 저장을 위한 인메모리 데이터 저장소입니다. 컨테이너 내부의 /etc/searxng/에 ./core-config/을 마운트하므로, 모든 설정은 호스트의 해당 디렉터리 하나에서 관리됩니다.

이제 .env를 편집하십시오. 제공된 예제 파일의 모든 줄은 주석 처리되어 있으며, 이 때문에 컨테이너는 모든 주소의 8080 포트에서 시작됩니다. 주석을 해제하고 다음 세 가지 항목을 설정하십시오.

SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080

SEARXNG_HOST=127.0.0.1은 중요한 설정입니다. 이 설정은 컨테이너가 모든 주소에서 응답하도록 하는 기본값 8080:8080 대신, 127.0.0.1:8080:8080 포트만 사용하도록 제한하여 인터넷에서 직접 접근할 수 없게 만듭니다. 이 설정을 건너뛰면 컨테이너가 시작되는 즉시 외부로 노출됩니다. Docker가 게시한 포트는 방화벽 규칙보다 우선 적용되기 때문입니다. 이 위험성에 대한 자세한 내용은 게시된 Docker 포트가 ufw를 우회하는 문제를 반드시 읽어보시기 바랍니다.

학습 단계에서는 SEARXNG_VERSION=latest를 사용해도 무방합니다. 하지만 실제 운영 중인 서버라면 태그를 고정하십시오. 2026년 10월 기준으로 릴리스 태그는 날짜 기반인 2026.10.4-d48c4b555과 같은 형식을 사용하므로, 태그를 고정하면 레지스트리의 변경 사항에 의존하지 않고 사용자가 직접 업데이트 시점을 결정할 수 있습니다. 이러한 관리 방식은 서버에서 장기간 운영하는 모든 서비스에 유효합니다. 자체 호스팅 RustDesk 릴레이 설정에서도 이미지 태그를 고정하는 이유가 바로 이것입니다. 원격 접속 서비스가 예고 없이 업데이트되면 가장 곤란한 순간에 서비스가 중단될 수 있기 때문입니다.

settings.yml: 핵심 설정

첫 실행 전에 core-config/settings.yml을 생성하십시오. use_default_settings: true는 SearXNG가 기본 제공되는 설정을 먼저 불러온 뒤 사용자가 작성한 키만 적용하도록 지시하므로, 설정 파일의 길이를 짧게 유지하고 새로운 옵션이 추가되는 업데이트 이후에도 설정을 보존할 수 있습니다.

비밀 키는 파일에 직접 입력해야 하므로 먼저 생성하십시오.

openssl rand -hex 32
use_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
    - json

secret_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 모드로 강화해도 문제없습니다. 공식 컨테이너의 엔트리포인트는 root 권한으로 시작하며, FORCE_OWNERSHIP이 기본값인 true로 설정되어 있으면 SearXNG가 시작되기 전에 마운트된 설정 디렉터리의 소유권을 컨테이너 내부의 searxng 사용자에게 넘기기 때문에 파일 읽기가 가능합니다. 이 과정에서 호스트 측의 core-config/ 소유자가 컨테이너의 사용자 ID로 변경되므로, 이후 파일을 수정할 때는 sudo을 사용해야 합니다.

스택을 시작하고 상태를 확인하십시오.

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.com

nginx -t은 리로드하기 전에 syntax is ok와 test is successful을 출력합니다. Certbot은 동일한 파일을 다시 작성하여 인증서와 함께 443 포트에서 수신 대기하도록 설정하고, 80 포트에서 리다이렉트를 추가합니다. 인증 기관은 HTTP를 통해 파일을 가져오는 방식으로 소유권을 확인하므로, search.example.com에 대한 DNS 레코드는 이미 이 서버를 가리키고 있어야 합니다. 갱신을 포함한 전체 절차는 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가 포함됩니다. 이는 요약기, 링크 검사기 또는 연구 루프를 구동하기에 충분한 정보입니다. 이러한 결과를 언어 모델에 전달하는 것은 보이는 것보다 더 큰 작업입니다. 검색 결과는 그 자체로 명령을 포함할 수 있는 신뢰할 수 없는 텍스트이기 때문이며, AI 에이전트를 SearXNG 인스턴스에 연결하는 방법에서 이를 상세히 다룹니다.

이는 에이전트 형태의 모든 것에 중요합니다. 언어 모델은 학습 데이터의 마감 시점이 존재하므로 현재에 관한 질문에 답하려면 실시간 검색이 필요하며, 상용 검색 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 = true

link_token = true를 설정하면 SearXNG는 실제 브라우저 세션만 가져올 수 있는 토큰을 발행하며, 이는 대부분의 단순 스크래퍼를 차단합니다. 공개 인스턴스는 며칠 내로 스크래퍼의 공격을 받게 될 것입니다. 엔진 오류도 예상하십시오. 더 많은 트래픽을 전달할수록 업스트림 엔진이 서버 주소로 CAPTCHA를 반환하는 시점이 빨라지기 때문입니다. 공개 SearXNG 인스턴스는 지속적인 관리가 필요한 작업입니다. 개인용 인스턴스는 그렇지 않으며, 이것이 2026년에 자가 호스팅할 가치가 있는 것들 목록에 자주 오르는 이유입니다. 해당 목록의 모든 항목이 인프라인 것은 아닙니다. Jellyfin 라이브러리를 90년대 대여점처럼 재구성하는 것은 동일한 nginx 블록 뒤의 동일한 컨테이너를 사용하지만, 워크플로우보다는 저녁 시간을 위한 용도입니다.

검색 결과가 나오지 않는 이유

인스턴스에서 /stats을(를) 엽니다. 여기에는 각 엔진의 오류율과 응답 시간이 나열되어 있으며, 검색 결과가 부족하다고 느껴질 때 가장 먼저 확인해야 할 곳입니다.

"Access denied" 또는 "CAPTCHA" 오류가 표시되는 엔진은 서버 주소를 차단한 상태입니다. 검색 엔진은 데이터 센터 대역의 주소를 스크래퍼(scraper)로 간주하기 때문에 이러한 차단은 흔히 발생합니다. SearXNG는 차단된 엔진을 재시도하는 대신 일정 시간 동안 중단시키므로, 차단된 엔진 하나가 결과에서 조용히 제외됩니다. settings.yml에서 해당 엔진을 비활성화하거나, 결과 누락을 감수하십시오. 해결책이 이것뿐인 것은 아닙니다. 일부 CAPTCHA 차단은 재시작 후에도 유지되는 해결 방법이 있습니다. 나머지 엔진은 여전히 응답합니다. 429 오류는 모호한 경우인데, 자체 제한 장치에서 발생했을 수도 있고 상위 엔진이 서버를 거부하여 발생했을 수도 있습니다. 설정을 변경하기 전에 로그 줄을 확인하여 어떤 경우인지 파악해야 합니다.

모든 엔진이 동시에 실패한다면, 컨테이너에 정상적인 아웃바운드 이름 확인 기능이 없거나 인터넷으로 향하는 경로가 없는 것입니다. 컨테이너 내부에서 이를 테스트하십시오.

docker compose exec core wget -qO- https://duckduckgo.com > /dev/null && echo ok

해당 확인 작업이 실패하기 시작할 때 서버 내부에서는 아무런 알림도 주지 않으므로, cron을 통해 실행하고 실패 시 직접 운영하는 ntfy 서버를 통해 휴대폰으로 알림을 푸시하도록 설정하십시오. 검색 결과가 부족해질 때까지 기다릴 필요가 없습니다.

FAQ

SearXNG를 사용하면 검색이 익명으로 처리됩니까?

SearXNG는 검색 엔진이 사용자의 브라우저가 아닌 서버의 요청을 확인하게 함으로써 검색 엔진으로부터 사용자의 신원을 숨깁니다. 하지만 서버가 검색 쿼리를 확인하는 것까지 숨기지는 않으며, 검색 엔진으로부터 서버의 주소를 숨기지도 않습니다. 단일 사용자 인스턴스의 경우 해당 주소에서 발생하는 모든 트래픽이 사용자의 것이므로, 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를 실행하십시오. Compose는 이미지가 변경된 컨테이너만 다시 생성하며 core-config/ 디렉터리는 그대로 유지하므로 settings.yml는 보존됩니다. use_default_settings: true은 사용자의 키를 기본 설정값과 병합하므로, 업스트림에 추가된 옵션은 파일이 손상되지 않고 적절한 값으로 적용됩니다.

여러 사람이 하나의 인스턴스를 공유할 수 있습니까?

예, 가능합니다. 이 경우 제한기를 켜고 public_instance: true를 설정해야 합니다. 환경 설정은 각 방문자의 브라우저에 저장되므로 별도로 관리할 계정은 없습니다. 인스턴스를 공개한 후 일주일 동안은 /stats를 모니터링하십시오. 검색 결과가 누락되는 것을 인지하기 훨씬 전부터 업스트림 검색 엔진이 서버의 요청을 거부하기 시작할 수 있기 때문입니다.

이 글이 도움이 되었나요?