SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-09-05

Sửa lỗi 429 và rate limit trong SearXNG

SearXNG trả lỗi 429 do limiter cục bộ hoặc engine chặn IP VPS. Đọc log để phân biệt hai nguyên nhân và sửa đúng cấu hình, không đoán mò.

Vì sao SearXNG trả về lỗi 429

Một instance SearXNG tự host trả về lỗi 429 vì 2 nguyên nhân không liên quan, và rate limit bạn cần xử lý thường không phải nguyên nhân bạn nghĩ. Nguyên nhân thứ nhất là cục bộ: limiter của chính SearXNG xác định request đến từ bot và trả về Too Many Requests với status 429. Nguyên nhân thứ hai nằm ở upstream: một search engine từ chối địa chỉ IP của server bạn. Trường hợp này đến với người dùng dưới dạng trang kết quả bị thiếu nội dung, không phải lỗi 429.

Hai trường hợp này không dùng chung cách xử lý. Limiter thuộc về bạn nên bạn có thể thay đổi nó. Việc upstream block xảy ra ở phía Google, vì vậy không có thiết lập nào trong settings.yml có thể gỡ block này. Log cho biết bạn đang gặp trường hợp nào trong khoảng 1 phút, nên hãy bắt đầu từ đó.

Hướng dẫn này giả định bạn đã cài container theo hướng dẫn một instance SearXNG tự host trên VPS của bạn. Tên của mọi setting bên dưới lấy từ tài liệu upstream và source hiện tại, đã kiểm tra vào tháng 8 năm 2026.

Đọc log trước khi thay đổi thiết lập

Mở một cửa sổ log rồi tái hiện sự cố.

cd ./searxng/
docker compose logs -f searxng-core

Các thông báo của limiter đến từ logger có tên searx.limiter và chứa địa chỉ IP. Một lần khớp blocklist có nội dung BLOCK 203.0.113.10: matched BLOCKLIST, còn một lần khớp allowlist có nội dung PASS 203.0.113.10: matched PASSLIST. Nếu limiter không thể truy cập counter store, log sẽ ghi The limiter requires Valkey, please consult the documentation. Điều này có nghĩa là không có bộ đếm nào hoạt động.

Mỗi lần kiểm tra bot riêng lẻ được ghi ở mức debug, nên mặc định bạn sẽ không thấy các dòng này. Bật debug cho một lần kiểm tra trong settings.yml:

general:
  debug: true

Sau đó, log sẽ thêm các dòng có dạng NOT OK (http_accept_language) bên cạnh network của client và nêu tên lần kiểm tra đã thất bại. Tắt debug lại ngay sau đó vì upstream yêu cầu không chạy instance đã deploy với debug được bật.

Engine bị lỗi sẽ có nội dung hoàn toàn khác. Log sẽ nêu tên engine thay vì địa chỉ IP. Lỗi phổ biến nhất là timeout:

HTTP requests timeout (search duration : 3.1 s, timeout: 3.0 s)

Cũng có một trang dành cho việc này. Khi enable_metrics giữ giá trị mặc định true, instance của bạn sẽ ghi lỗi engine tại /stats/errors, còn /preferences liệt kê những engine hiện đang phản hồi. Nếu /stats/errors đã đầy nhưng log không có dòng searx.limiter nào, limiter không phải nguyên nhân gây lỗi.

Cố định version trước khi debug bất kỳ thứ gì

Cấu hình container từ upstream gồm 2 file.

mkdir -p ./searxng/core-config/
cd ./searxng/

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 kéo docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}. Biến chưa được set có nghĩa là latest, còn latest có nghĩa là instance sẽ thay đổi trong lần docker compose pull tiếp theo. Vì vậy, một setting hoạt động tuần trước có thể không còn khớp với code đọc nó. Tag của SearXNG chứa ngày và commit. Tính đến tháng 8 năm 2026, tag trong ví dụ của upstream .env.example2026.3.25-541c6c3cb, vì vậy hãy đặt một tag cụ thể trong .env:

SEARXNG_VERSION=2026.3.25-541c6c3cb

Kiểm tra các tag đã publish và pin release mà bạn đã thực sự test, sau đó debug trên một target cố định. File .env này cũng chứa secret key của bạn, vì vậy hãy đọc cách file env và secret hoạt động trong Docker Compose trước khi commit thư mục đó ở bất kỳ đâu.

Limiter cần Valkey, nếu không sẽ không chạy

Bộ giới hạn đếm số request theo từng client, và các bộ đếm này phải được chia sẻ giữa các worker process. Store đó là Valkey, fork được duy trì của Redis. Các hướng dẫn SearXNG cũ gọi setting này là redis:. Các release hiện tại đọc valkey:, vì vậy hãy lấy đúng tên key từ tài liệu hiện tại thay vì từ một bài đăng cũ. Một số trang còn cũ hơn nữa và mô tả Searx thay vì SearXNG. Đây là hai codebase khác nhau với limiter khác nhau, vì vậy hãy xác định trang đó viết cho project nào trong hai project trước khi copy một config block từ đó.

use_default_settings: true
server:
  secret_key: "change-this-value"
  limiter: true
  public_instance: false
valkey:
  url: valkey://searxng-valkey:6379/0

File compose upstream đã chạy service searxng-valkey bằng image docker.io/valkey/valkey:9-alpine, nên hostname đó sẽ được phân giải trong compose network. Có thể đặt cùng giá trị bằng environment variable SEARXNG_VALKEY_URL. URL Unix socket (unix:///path/to/socket.sock?db=0) cũng hoạt động khi SearXNG và Valkey dùng chung một host.

Việc xảy ra khi thiếu store phụ thuộc vào một key khác. Với public_instance: false, limiter ghi log lỗi Valkey rồi dừng, nên instance vẫn tiếp tục phục vụ nhưng hoàn toàn không rate limiting. Với public_instance: true, process gọi sys.exit(1) thay thế, vì một instance đang mở nhưng bot protection bị lỗi sẽ thu thập CAPTCHA (completely automated public turing test to tell computers and humans apart) từ mọi engine trong vòng một ngày. Nếu container restart liên tục ngay sau khi bạn đặt public_instance: true thì đây chính là nguyên nhân. Dòng cuối cùng trước mỗi lần thoát sẽ nêu rõ Valkey.

Bộ giới hạn thực sự đếm gì

ChartSearXNG limiter: requests allowed per client IP, defaults in ip_limit.py
The data behind this chart
[
  {
    "label": "Burst, normal client",
    "max_requests": 15,
    "window": "20 seconds"
  },
  {
    "label": "Burst, flagged client",
    "max_requests": 2,
    "window": "20 seconds"
  },
  {
    "label": "Sustained, normal client",
    "max_requests": 150,
    "window": "10 minutes"
  },
  {
    "label": "Sustained, flagged client",
    "max_requests": 10,
    "window": "10 minutes"
  },
  {
    "label": "Any non-HTML format",
    "max_requests": 4,
    "window": "1 hour"
  },
  {
    "label": "Flagged requests before block",
    "max_requests": 3,
    "window": "30 days"
  }
]

Client thông thường được phép gửi 15 request trong cửa sổ burst 20 giây và 150 request trong cửa sổ 10 phút. Khi một request bị đánh dấu là đáng ngờ, cùng client đó chỉ còn được gửi 2 request trong mỗi cửa sổ burst. Hàng cuối cùng là mức nghiêm ngặt nhất: sau 3 request bị đánh dấu trong cửa sổ 30 ngày, địa chỉ đó sẽ bị redirect về trang bắt đầu thay vì được tìm kiếm, và log ghi BLOCK: too many request from ... in SUSPICIOUS_IP_WINDOW (redirect to /).

Các con số này là hằng số trong searx/botdetection/ip_limit.py. Chúng không phải setting và limiter.toml không cung cấp tùy chọn để thay đổi, nên muốn đổi phải sửa source. /etc/searxng/limiter.toml kiểm soát các prefix của địa chỉ dùng để nhóm client, danh sách trusted proxy, kiểm tra link token tùy chọn, cùng các pass list và block list.

Một request bị đánh dấu là đáng ngờ dựa trên các header check. Mỗi check có một tên, và bạn sẽ thấy tên đó trong debug log:

  • http_accept: header Accept không chứa text/html.
  • http_accept_encoding: header Accept-Encoding không chỉ định gzip hoặc deflate.
  • http_accept_language: không có header Accept-Language.
  • http_connection: header Connection được đặt thành close.
  • http_user_agent: User-Agent bị thiếu hoặc khớp với một bot pattern đã biết.
  • http_sec_fetch: header Sec-Fetch-Mode hoặc Sec-Fetch-Dest không có giá trị mà browser gửi.

Browser gửi tất cả các header này. Một lệnh gọi curl thông thường hầu như không gửi header nào trong số đó, nên request test viết thủ công sẽ bị đánh dấu ngay lần đầu, trong khi cùng một tìm kiếm vẫn hoạt động trong một tab browser. Vì vậy, tình huống “hoạt động trong browser nhưng script nhận 429” là kết quả bình thường, không phải lỗi khó hiểu.

Limiter chặn tất cả người dùng phía sau reverse proxy

Đây là cách phổ biến nhất khiến một instance đang hoạt động bị lỗi. SearXNG lấy địa chỉ client từ IP không được tin cậy đầu tiên trong X-Forwarded-For, nếu không có thì dùng X-Real-IP, rồi tiếp tục dùng địa chỉ đã mở kết nối. Việc có tin cậy các header đó hay không được quyết định bởi trusted_proxies trong limiter.toml.

Nếu địa chỉ của proxy không nằm trong danh sách đó, các header sẽ bị bỏ qua và mọi visitor đều được ghi nhận bằng địa chỉ của proxy. Khi đó tất cả dùng chung một bộ đếm. Toàn bộ site sẽ bị chặn khi tổng số request vượt 150 trong 10 phút. Chỉ một người reload trang kết quả vài lần cũng có thể khiến mọi người khác bị chặn theo.

Tin cậy quá nhiều còn nguy hiểm hơn. Nếu thêm một public range vào danh sách, bất kỳ visitor nào cũng có thể gửi header X-Forwarded-For của riêng họ và chọn một identity mới cho mỗi request. Điều này vô hiệu hóa limiter với bất kỳ ai biết cách thử. Chỉ thêm địa chỉ mà proxy của chính bạn dùng để kết nối. Trong Docker, đó thường là một bridge network bên trong 172.16.0.0/12, và dòng này mặc định được comment out.

[botdetection]
ipv4_prefix = 32
ipv6_prefix = 48

trusted_proxies = [
  '127.0.0.0/8',
  '::1',
  '172.16.0.0/12',
]

Proxy cũng phải gửi các header đó. Nginx không tự thêm chúng:

location / {
    proxy_pass http://127.0.0.1:8080;

    proxy_set_header Host              $host;
    proxy_set_header Connection        $http_connection;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
}

Caddy và Traefik tự thiết lập các forwarded header, nên với các proxy này bạn chỉ cần cấu hình phần trusted_proxies. Các đánh đổi được trình bày trong cách chọn reverse proxy cho một service tự host. Để kiểm tra cả hai kiểu cấu hình, bật debug, dùng điện thoại với mobile data để thực hiện một lần tìm kiếm, rồi xác nhận network trong dòng log là địa chỉ của điện thoại, không phải địa chỉ của proxy.

Agent của bạn được 4 request API mỗi giờ

JSON output bị tắt theo mặc định, nên cần thêm tùy chọn này cho agent:

search:
  formats:
    - html
    - json

Bây giờ hãy đọc lại dòng trong bảng. Mọi request yêu cầu format khác HTML đều được tính trong cửa sổ riêng: 4 request mỗi 1 hour, trên mỗi địa chỉ. Một research agent có thể dùng hết giới hạn này trong một tác vụ, và mọi lần gọi sau đó sẽ trả về 429. Không thể tăng giới hạn vì giá trị này nằm trong source.

Cách sửa phù hợp là cho limiter biết client này không phải client lạ. Thêm địa chỉ của client vào pass list trong limiter.toml:

[botdetection.ip_lists]
block_ip = []

pass_ip = [
  '10.8.0.0/24',
]

pass_searxng_org = true

pass_ip được ưu tiên hơn mọi phương thức khác. Vì vậy, client nằm trong allowlist cũng bỏ qua các lần kiểm tra header và một lệnh gọi curl không có tham số vẫn hoạt động. Giữ range nhỏ nhất có thể. Ưu tiên dùng subnet của VPN hoặc network của container thay vì một địa chỉ có thể định tuyến. Cách sửa phù hợp khác là hoàn toàn không cho agent đi qua public path: trỏ agent đến địa chỉ container trên internal network, nơi proxy và limiter của proxy không nhìn thấy traffic. Cách kết nối này được hướng dẫn trong cung cấp cho AI agent skill tìm kiếm SearXNG.

Không nên trỏ agent đến một public instance do người khác vận hành. Đây là cách nhanh nhất khiến IP của một tình nguyện viên bị các upstream engine block. Đây cũng là lý do JSON format bị tắt theo mặc định ngay từ đầu.

Khi engine chặn bạn thay vào đó

ChartHow long SearXNG suspends an engine, search.suspended_times defaults
The data behind this chart
[
  {
    "label": "SearxEngineTooManyRequests",
    "suspended_seconds": 3600,
    "roughly": "1 hour"
  },
  {
    "label": "SearxEngineAccessDenied",
    "suspended_seconds": 86400,
    "roughly": "1 day"
  },
  {
    "label": "SearxEngineCaptcha",
    "suspended_seconds": 86400,
    "roughly": "1 day"
  },
  {
    "label": "recaptcha_SearxEngineCaptcha",
    "suspended_seconds": 604800,
    "roughly": "7 days"
  },
  {
    "label": "cf_SearxEngineCaptcha",
    "suspended_seconds": 1296000,
    "roughly": "15 days"
  }
]

Khi một engine trả về 429 hoặc trang CAPTCHA, SearXNG sẽ phát sinh exception tương ứng và tạm thời ngừng gửi request đến engine đó. Phản hồi too-many-requests sẽ tạm ngưng engine trong 3600 giây. Phản hồi CAPTCHA thông thường hoặc access-denied sẽ tạm ngưng engine trong 1 day. CAPTCHA được Cloudflare cung cấp sẽ tạm ngưng engine trong 15 days, lâu nhất trong danh sách mặc định, vì phản hồi này cho biết việc chặn nằm ở edge và retry sẽ không có tác dụng. Bạn gặp row CAPTCHA nào trong 3 row sẽ quyết định nên thử cách nào tiếp theo, và lỗi CAPTCHA có nhóm cách khắc phục riêng sau khi xác định được instance của bạn đã ghi nhận exception nào.

Các lỗi thông thường dùng thiết lập khác. Timeout hoặc lỗi parse sẽ tạm ngưng engine trong thời gian ngắn được tính từ search.ban_time_on_fail. Giá trị mặc định là 5 giây và bị giới hạn bởi search.max_ban_time_on_fail ở mức 120 giây. Vì vậy, một engine chậm sẽ tự hoạt động lại trong vài phút, còn engine bị chặn sẽ ngừng hoạt động trong nhiều giờ. Khác biệt này giải thích một triệu chứng thường bị xem là ngẫu nhiên: kết quả vẫn bình thường, rồi kết quả từ một engine biến mất trong phần còn lại của buổi chiều.

Bạn nên xử lý timeout trước khi quy trách nhiệm cho engine. Giá trị mặc định của request_timeout là 2.0 giây. Mức này khá thấp đối với một VPS nhỏ nằm xa edge server gần nhất của engine.

outgoing:
  request_timeout: 3.0
  max_request_timeout: 10.0
engines:
  - name: bing
    timeout: 5.0

request_timeout là giá trị mặc định cho mọi engine, max_request_timeout là giới hạn trên, còn một engine riêng lẻ có thể đặt timeout riêng. Tăng các giá trị này sẽ đánh đổi latency của trang để giảm lỗi. Vì vậy, hãy tăng từng nửa giây và theo dõi /stats/errors thay vì tăng thẳng lên 10.

Với engine thực sự đang chặn địa chỉ của bạn, hãy xóa engine đó. Mỗi lần tìm kiếm đều phải chờ engine chậm nhất. Giữ một engine luôn bị tạm ngưng chỉ làm tăng latency và không trả về kết quả.

use_default_settings:
  engines:
    remove:
      - google

Áp dụng thay đổi bằng docker compose restart searxng-core, sau đó thực hiện vài lần tìm kiếm và tải lại /stats/errors. Nếu trang trống sau 5 phút sử dụng thực tế, thay đổi đã có hiệu lực.

Địa chỉ IP của datacentre sẽ bị xem là bot

Địa chỉ VPS của bạn thuộc một dải địa chỉ hosting, và các công cụ tìm kiếm lớn thường đánh giá những dải này là lưu lượng tự động. Một số công cụ sẽ hiển thị CAPTCHA cho mọi request từ địa chỉ như vậy, bất kể header có hợp lệ đến đâu hoặc tốc độ request có chậm đến đâu. Không có setting nào trong settings.yml thay đổi được đánh giá đó. Việc các công cụ tìm kiếm nhìn thấy server của bạn thay vì người đang nhập truy vấn cũng là toàn bộ đánh đổi về privacy khi bạn tự host, và SearXNG thực sự che giấu được bao nhiêu là nội dung đáng đọc trước khi bạn cho rằng nó che giấu nhiều hơn.

Bạn có thể thay đổi danh sách công cụ tìm kiếm được truy vấn và quyết định instance có được công khai hay không. Một instance private chỉ được một hộ gia đình sử dụng hiếm khi bị chặn. Một instance public chạy trên hosting IP sẽ thường xuyên bị các công cụ nghiêm ngặt nhất suspend, và đó là trạng thái bình thường của phần mềm chứ không phải lỗi trong config của bạn. SearXNG có thể chuyển các request đến công cụ tìm kiếm qua proxy bằng outgoing.proxies hoặc outgoing.using_tor_proxy, nhờ đó network traffic đi ra từ một địa chỉ khác. Exit node và các proxy pool giá rẻ thường bị đánh giá kém hơn các dải hosting, vì vậy hãy dự đoán rằng thay đổi này có thể làm kết quả tệ hơn.

Theo dõi instance để phát hiện sự cố sớm

SearXNG vẫn trả lời trên port của nó ngay cả khi tất cả engine đều bị tạm dừng. Vì vậy, một uptime check chỉ kiểm tra status code vẫn báo trạng thái bình thường trong khi instance không trả về dữ liệu. Hãy kiểm tra cả nội dung: gửi một truy vấn tìm kiếm thực tế và đối chiếu một từ mà bạn mong đợi xuất hiện trong response body. Theo dõi từ khóa bằng Uptime Kuma thực hiện việc này mà không cần thêm công cụ. Sau mỗi lần nâng version, hãy kiểm tra cả /stats/errors, vì HTML của các engine có thể thay đổi và parser có thể hỏng mà không liên quan đến rate limit.

FAQ

Vì sao SearXNG trả về 429 cho mọi người dùng sau khi tôi đặt nó sau reverse proxy?

Vì bộ giới hạn đang tính reverse proxy là client. SearXNG chỉ đọc X-Forwarded-For khi địa chỉ kết nối được liệt kê trong trusted_proxies tại /etc/searxng/limiter.toml. Nếu chưa liệt kê, mọi người dùng sẽ dùng chung một bộ đếm và cùng vượt ngưỡng 150 requests trong 10 phút. Hãy thêm địa chỉ mà proxy dùng để kết nối. Trong Docker, địa chỉ này thường thuộc dải bridge 172.16.0.0/12. Đồng thời bảo đảm proxy gửi X-Real-IPX-Forwarded-For. Không bao giờ liệt kê một dải mạng mà bạn không kiểm soát, vì trusted network cho phép bất kỳ người dùng nào đặt header đó và chọn một danh tính mới cho mỗi request.

Bộ giới hạn của SearXNG cho phép bao nhiêu API requests mỗi giờ?

4 requests cho mỗi địa chỉ IP mỗi giờ. Mọi request yêu cầu format khác HTML sẽ được tính trong một cửa sổ 1 giờ riêng. Giới hạn này được đặt trong searx/botdetection/ip_limit.py thay vì limiter.toml, nên không thể tăng từ config. Một agent hoặc script sẽ chạm giới hạn này trong một tác vụ. Thêm địa chỉ của client vào pass_ip trong limiter.toml, hoặc truy cập instance qua một network nội bộ nơi bộ giới hạn không nhìn thấy request.

Vì sao kết quả tìm kiếm của tôi trả về rỗng mà không có lỗi 429?

Các engine đang từ chối server của bạn, không phải người dùng của bạn. Mở /stats/errors trên instance của bạn. File này nêu từng engine đã fail và lý do. Mục CAPTCHA hoặc access-denied có nghĩa là engine đó đã chặn địa chỉ IP của server bạn. Sau đó SearXNG sẽ tạm dừng engine: 1 giờ sau khi nhận phản hồi too-many-requests và 1 ngày sau khi nhận CAPTCHA. Không có setting cục bộ nào gỡ được block từ upstream. Vì vậy, hãy xóa các engine chặn địa chỉ của bạn và giữ lại các engine vẫn trả lời.

Tôi có nên bật bộ giới hạn trên một instance private không?

Nếu chỉ có bạn truy cập instance, hãy để limiter: false. Nó thêm dependency Valkey và chặn các script của chính bạn, trong khi không bảo vệ khỏi loại traffic nào bạn thực sự nhận. Hãy bật nó ngay khi instance có địa chỉ public, cùng với public_instance: true. Cặp setting này được thiết kế như vậy: khi có public_instance: true nhưng Valkey không hoạt động, process sẽ thoát với status 1 thay vì chạy mà không được bảo vệ.