SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

Tự host SearXNG trên VPS với Docker Compose

Chạy SearXNG riêng trên VPS bằng Docker Compose, cấu hình settings.yml, limiter, nginx với TLS và JSON search API để script của bạn gọi.

Bạn đang xây dựng gì

Tự host SearXNG cung cấp cho bạn một search engine riêng chạy trên server của chính bạn. SearXNG là một metasearch engine: nó nhận query của bạn, gửi query đến các engine khác như Google, Bing, DuckDuckGo và Wikipedia, rồi gộp kết quả trả về thành một trang kết quả. SearXNG không tạo profile và không đặt tracking cookie, vì máy duy nhất lưu query của bạn là máy của bạn. Nếu bạn đã thấy các hướng dẫn cũ về một dự án chỉ có tên Searx, đó là dự án mà SearXNG được fork từ đó. Dự án này không còn được maintain từ September 2023, khi repository của chính nó thông báo rằng Searx không còn được maintain. Commit duy nhất sau đó, một ghi chú tài liệu vào May 2026, cũng không thay đổi điều này. Vì vậy, hãy kiểm tra trạng thái của cả hai trước khi làm theo một hướng dẫn.

Stack này nhỏ. Chỉ có hai container, một settings file và một reverse proxy. Nó có thể chạy ổn trên một VPS nhỏ. Điều này không đúng với mọi self-hosted service: các thư viện ảnh được so sánh trong PhotoPrism và Immich xác định mức RAM tối thiểu theo indexer thay vì web app. Quyết định thực sự là instance sẽ private hay public. Private nghĩa là chỉ bạn và các script của bạn có thể truy cập. Public nghĩa là bất kỳ ai trên Internet cũng có thể gửi query. Lựa chọn này thay đổi các security settings, vì vậy hãy quyết định trước khi nhập bất kỳ lệnh nào. Mặc định nên chọn private.

Có một lý do khác để chạy SearXNG. Instance SearXNG cung cấp JSON, nên mọi script hoặc AI agent bạn viết đều có một search API do bạn kiểm soát, không cần key, không tính phí theo từng query và không có email thông báo quota.

Cài đặt SearXNG bằng Docker Compose

Dự án phát hành một container image và một file Compose. Hãy tải cả hai về một server Ubuntu 24.04 mới đã cài Docker Engine và Compose plugin. Nếu bạn chưa quen với Docker, hãy bắt đầu từ Những kiến thức cơ bản về Docker Compose trên VPS rồi quay lại.

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

File Compose định nghĩa 2 service. core là chính SearXNG, còn valkey là data store trong memory dùng để rate limiting và lưu state ngắn hạn. File này mount ./core-config/ vào /etc/searxng/ bên trong container, vì vậy mọi cấu hình của bạn nằm trong một thư mục duy nhất trên host.

Bây giờ hãy sửa .env. Mọi dòng trong ví dụ được cung cấp đều bị comment, nên container khởi động trên port 8080 tại mọi địa chỉ. Hãy bỏ comment và đặt giá trị cho 3 dòng sau.

SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080

SEARXNG_HOST=127.0.0.1 là dòng quan trọng nhất. Dòng này đặt published port thành 127.0.0.1:8080:8080 thay vì 8080:8080 dạng trần. Docker sẽ publish 8080:8080 trên mọi địa chỉ, nên container chỉ trả lời tại địa chỉ loopback và Internet không thể truy cập trực tiếp. Nếu bỏ qua bước này, container sẽ bị expose ngay khi khởi động, vì published Docker port được chèn trước các firewall rule của bạn. Đây là một bẫy đáng đọc đầy đủ: published Docker port bypass ufw.

SEARXNG_VERSION=latest phù hợp trong lúc bạn đang tìm hiểu. Trên server quan trọng, hãy pin tag. Tính đến tháng 10 năm 2026, các release tag dùng ngày tháng và có dạng 2026.10.4-d48c4b555, vì vậy deployment được pin sẽ chỉ upgrade khi bạn quyết định, thay vì tự thay đổi khi registry cập nhật. Cách làm này cũng hữu ích với mọi thành phần chạy lâu dài khác trên máy, đó là lý do một RustDesk relay tự host cũng pin image tag: một lần upgrade không được giám sát đối với remote access service sẽ xuất hiện đúng vào thời điểm tệ nhất.

settings.yml: các phần quan trọng

Tạo core-config/settings.yml trước lần khởi động đầu tiên. use_default_settings: true yêu cầu SearXNG nạp các giá trị mặc định đi kèm, sau đó chỉ áp dụng những key bạn đã ghi. Nhờ vậy, file vẫn ngắn và không bị ảnh hưởng khi các bản nâng cấp thêm option mới.

Trước tiên, hãy tạo secret vì giá trị này sẽ được ghi trực tiếp vào file.

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 dùng để ký dữ liệu session và token. Giá trị mặc định đi kèm là chuỗi nguyên văn ultrasecretkey. Nếu giữ nguyên, bất kỳ ai biết giá trị mặc định đó đều có thể giả mạo các token này. Hãy thay đổi một lần rồi giữ nguyên: thay đổi về sau sẽ xóa mọi preference đã lưu.

base_url phải là địa chỉ HTTPS public, có dấu gạch chéo ở cuối. SearXNG dùng địa chỉ này để ghi vào các link được render. Nếu để trỏ về localhost, link "trang tiếp theo" trong trình duyệt remote sẽ trỏ đến chính máy của người đọc và không hoạt động.

formats quyết định các loại output mà web endpoint sẽ trả về. json không có trong danh sách mặc định, nên request JSON sẽ trả về 403 cho đến khi bạn thêm nó. image_proxy: true chuyển tiếp thumbnail kết quả qua server của bạn, nhờ đó các site lưu những ảnh này không nhìn thấy địa chỉ của visitor.

valkey.url dùng hostname valkey vì đây là tên service trong file Compose. Compose đặt cả hai container trên cùng một network, nơi các service name có thể được phân giải. Nếu trỏ đến localhost, limiter sẽ lỗi vì bên trong container core, localhost là chính container đó.

Secret nằm trong một file thường, nên hãy bảo vệ cả directory chứa file. chmod 750 /opt/searxng ngăn các user khác trên host truy cập. Bạn cũng có thể đặt core-config/settings.yml về mode 600 mà không gây lỗi: entrypoint của official container khởi động với root và, khi FORCE_OWNERSHIP vẫn giữ giá trị mặc định là true, chuyển quyền sở hữu config directory được mount cho user searxng của container trước khi SearXNG khởi động. Vì vậy file vẫn có thể được user đó đọc. Tác động phụ xuất hiện trên host: bước này cũng đổi owner của core-config/ thành user ID của container, nên sau đó bạn sẽ cần dùng sudo để chỉnh sửa file.

Khởi động stack rồi kiểm tra.

cd /opt/searxng
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080/

docker compose ps phải hiển thị cả hai container ở trạng thái running. curl phải trả về HTTP/1.1 200 OK. Nếu không có response, hãy đọc docker compose logs core vì lỗi YAML trong settings.yml sẽ xuất hiện ở đó dưới dạng lỗi parse, kèm theo số dòng.

Đặt dịch vụ phía sau nginx với TLS

Container chỉ lắng nghe trên loopback, nên nginx giúp dịch vụ có thể được truy cập. nginx cũng thêm transport layer security (TLS). Viết /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 in ra syntax is ok và test is successful trước khi bạn reload. Certbot sẽ ghi lại chính file đó để lắng nghe trên 443 bằng certificate, đồng thời thêm redirect từ port 80. DNS record của search.example.com phải trỏ đến server này từ trước, vì certificate authority xác minh quyền sở hữu bằng cách tải một file qua HTTP. Hướng dẫn đầy đủ, bao gồm cả việc gia hạn, có trong hướng dẫn Certbot và nginx cho Ubuntu 24.04.

Hai forwarding header này không phải để trang trí. Nếu thiếu X-Forwarded-For và X-Real-IP, mọi request đến SearXNG đều mang địa chỉ của proxy. Khi đó rate limiter thấy một client tạo ra toàn bộ traffic và không thể phân biệt các visitor.

Vì sao script và agent cần JSON search API

Với json trong formats, cùng một endpoint dùng để render trang cũng trả về dữ liệu có cấu trúc.

curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
  | jq -r '.results[0:5][] | .url'

Bạn nhận lại một object có array results. Mỗi phần tử chứa url, title, content và engine đã cung cấp kết quả đó, cùng với answers, infoboxes và suggestions. Như vậy là đủ để cung cấp dữ liệu cho summariser, link checker hoặc một vòng lặp nghiên cứu. Đưa các kết quả này cho language model phức tạp hơn vẻ ngoài, vì kết quả tìm kiếm là văn bản không đáng tin cậy và có thể chứa các instruction riêng. Trỏ AI agent vào instance SearXNG của bạn trình bày chi tiết cách xử lý vấn đề này.

Điều này quan trọng với mọi hệ thống có agent. Language model có training cutoff, nên cần live search để trả lời các câu hỏi về hiện tại. Các search API thương mại tính phí theo từng query và rate limit rất chặt. Một instance local chỉ tốn một container trên server mà bạn đã trả tiền, và các query không rời khỏi máy đó. Nếu bạn tích hợp tool vào model, cùng lý do này dẫn đến việc chạy MCP server trên VPS, trong đó search tool thường là tool đầu tiên được thêm vào.

Có 2 quy tắc khi dùng API. Giữ instance ở chế độ private. Hãy bind phía API vào loopback address hoặc private network, rồi chỉ cho các host của bạn truy cập. Cách này cũng giống với yêu cầu của location tracker tự host đối với endpoint mà điện thoại gửi vị trí đến. Sau đó, hãy query với tốc độ vừa phải. SearXNG chuyển tiếp request của bạn đến các search engine thực tế, nên một script chạy 100 query mỗi giây sẽ khiến Google chặn server của bạn.

Bộ giới hạn và những thay đổi khi public instance

Bộ giới hạn là cơ chế phòng chống bot của SearXNG. Nó theo dõi request header, địa chỉ và tần suất request, rồi loại bỏ traffic có dấu hiệu được tạo tự động. Nó cần Valkey để lưu trạng thái đó, nên Valkey được đưa vào Compose file.

Trên private instance, hãy giữ limiter: false. Các script của bạn về bản chất là traffic tự động, nên bộ giới hạn sẽ chặn đúng những JSON call mà bạn triển khai instance để phục vụ. Thay vào đó, hãy để reverse proxy xử lý việc kiểm soát truy cập: một cặp allow và deny trong location của nginx, HTTP basic authentication hoặc firewall chỉ cho phép các server khác của bạn truy cập. Nếu cần truy cập private instance từ laptop thường xuyên chuyển giữa các network, đặt địa chỉ onion v3 phía trước instance là lựa chọn thứ tư, vì tor kết nối đến cùng loopback port mà không expose thêm bất cứ thứ gì ra internet.

Nếu bạn public instance cho người khác sử dụng, hãy bật cả hai switch.

server:
  limiter: true
  public_instance: true

Bạn có thể kiểm soát chi tiết hơn trong core-config/limiter.toml; container đọc file này tại /etc/searxng/limiter.toml. Chỉ khai báo các key bạn muốn thay đổi. Khi chạy sau proxy, bạn phải khai báo proxy, nếu không bộ giới hạn sẽ coi địa chỉ nginx của bạn là client duy nhất đang gửi traffic lạm dụng.

[botdetection]
trusted_proxies = [
  '127.0.0.0/8',
  '::1',
]

[botdetection.ip_limit]
link_token = true

link_token = true khiến SearXNG tạo token mà chỉ một browser session thật mới lấy được, nhờ đó chặn được phần lớn scraper đơn giản. Hãy dự kiến public instance sẽ thu hút các scraper này trong vài ngày. Cũng hãy dự kiến engine error, vì càng forward nhiều traffic thì các upstream engine càng sớm bắt đầu trả CAPTCHA cho địa chỉ server của bạn. Vận hành một public SearXNG instance là công việc liên tục. Private instance thì không như vậy, nên nó thường nằm trong các danh sách ngắn về những thứ đáng tự host trong 2026. Tuy nhiên, không phải mục nào trong các danh sách đó cũng là infrastructure: xây lại thư viện Jellyfin thành một cửa hàng cho thuê băng đĩa thập niên 90 mà bạn có thể đi dạo vẫn chỉ là một container phía sau cùng block nginx, nhưng hướng đến một buổi tối thay vì một workflow.

Khi tìm kiếm không trả về kết quả

Mở /stats trên instance của bạn. Trang này liệt kê từng engine cùng tỷ lệ lỗi và thời gian phản hồi. Đây là nơi đầu tiên cần kiểm tra khi kết quả tìm kiếm có vẻ quá ít.

Engine hiển thị lỗi "Access denied" hoặc "CAPTCHA" đã chặn địa chỉ server của bạn. Tình trạng này phổ biến với các địa chỉ thuộc dải của data centre, vì search engine thường cho rằng chúng được scraper sử dụng. Sau đó, SearXNG sẽ tạm ngưng engine bị lỗi trong một khoảng thời gian thay vì thử lại. Vì vậy, một engine bị chặn có thể âm thầm biến mất khỏi kết quả tìm kiếm. Hãy tắt engine đó trong settings.yml hoặc chấp nhận việc mất nó. Đây không phải là 2 lựa chọn duy nhất, vì một số CAPTCHA block có cách khắc phục vẫn giữ hiệu lực sau khi restart. Các engine còn lại vẫn tiếp tục trả lời. Mã lỗi 429 là trường hợp khó xác định, vì nó có thể xuất phát từ limiter của chính bạn hoặc từ upstream engine từ chối server của bạn. Dòng log cho biết bạn đang gặp trường hợp nào trong 2 trường hợp đó trước khi bạn bắt đầu thay đổi settings.

Nếu mọi engine đều fail cùng lúc, container không có outbound name resolution hoạt động hoặc không có route ra internet. Hãy kiểm tra từ bên trong container.

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

Không có thành phần nào trên máy cho bạn biết khi kiểm tra này bắt đầu fail. Vì vậy, hãy chạy nó từ cron và để một lần fail gửi alert đến điện thoại của bạn từ ntfy server do chính bạn vận hành, thay vì chờ đến khi nhận ra kết quả tìm kiếm đã quá ít.

FAQ

SearXNG có làm cho các lượt tìm kiếm của tôi ẩn danh không?

SearXNG ẩn danh tính của bạn đối với các engine mà nó truy vấn, vì chúng thấy server của bạn gửi request thay vì browser. Nó không ẩn truy vấn khỏi server của bạn và cũng không ẩn server của bạn khỏi các engine đó. Với một instance chỉ dành cho một người dùng, toàn bộ traffic từ địa chỉ đó là của bạn, nên chính địa chỉ này trở thành identifier. Traffic giữa browser và instance của bạn được bảo vệ bằng TLS certificate. Mức độ bảo vệ trước ISP, operator của một instance public và chính các engine được giải thích trong SearXNG thực sự ẩn những gì.

Tại sao request JSON trả về 403 Forbidden?

Có 2 nguyên nhân và cả hai đều liên quan đến cấu hình. Hoặc json chưa có trong danh sách formats dưới search: trong settings.yml, đây là trạng thái mặc định, hoặc limiter đang bật và đã phân loại script của bạn là bot. Trước tiên hãy thêm format, restart bằng docker compose restart core, rồi thử lại. Nếu vẫn lỗi, đặt limiter: false và kiểm soát quyền truy cập tại reverse proxy.

Tôi có cần container Valkey nếu tắt limiter không?

Hãy để container này chạy. SearXNG vẫn hoạt động khi không có nó, nhưng không thể bật limiter sau này nếu thiếu container, và container này cũng lưu các state ngắn hạn khác. Container có kích thước nhỏ và chỉ lưu cached data, nên xóa nó chỉ tiết kiệm được rất ít nhưng làm mất tùy chọn này.

Làm cách nào để update SearXNG?

Chạy docker compose pull rồi docker compose up -d trong /opt/searxng. Compose sẽ tạo lại mọi container có image thay đổi và giữ nguyên directory core-config/, nên settings.yml vẫn tồn tại. Vì use_default_settings: true merge các key của bạn lên trên các giá trị mặc định được release, những option được upstream thêm vào sẽ nhận giá trị hợp lý thay vì làm hỏng file.

Nhiều người có thể dùng chung một instance không?

Có. Đây là trường hợp bạn bật limiter và đặt public_instance: true. Preferences được lưu trong browser riêng của từng visitor, nên không cần quản lý account. Hãy monitor /stats trong 1 tuần sau khi mở quyền truy cập, vì các engine upstream thường bắt đầu từ chối server của bạn từ lâu trước khi bạn nhận ra kết quả tìm kiếm bị thiếu.

Bài viết này có hữu ích không?