Docker 部署 SearXNG 并添加自定义 Token 认证网关实战指南
Docker 部署 SearXNG 并添加自定义 Token 认证网关实战指南
本文记录了如何在 Linux 服务器上使用 Docker 部署隐私搜索引擎 SearXNG,将其端口修改为 8003,并为了防止公网滥用,添加一个基于 Python Flask 的轻量级 Token 认证网关。同时详细记录了在部署过程中遇到的 “Internal Server Error”、“样式丢失 (404)” 以及 “Too Many Requests” 等问题的排查与解决。
一、环境准备与目录结构
首先创建必要的目录结构:
mkdir -p /opt/searxng/searxng
mkdir -p /opt/searxng/auth-gateway
cd /opt/searxng
二、核心配置
1. SearXNG 配置文件 (纯净版)
编辑 /opt/searxng/searxng/settings.yml。
⚠️ 注意: 配置文件对 YAML 格式缩进极为敏感。为了避免 Vim 粘贴时注释错乱导致的 Internal Server Error (500),这里提供一份无注释的纯净版配置。请直接覆盖使用。
use_default_settings: true
general:
debug: false
instance_name: "SearXNG-Private"
privacypolicy_url: false
donation_url: false
contact_url: false
enable_metrics: true
search:
safe_search: 0
autocomplete: "baidu"
default_lang: "zh-CN"
ban_time_on_fail: 5
max_ban_time_on_fail: 120
formats:
- html
- json
server:
# 请修改这里为你自己的随机密钥
secret_key: "change-this-to-a-random-secret-key"
# 关键:必须关闭限制,否则会被网关触发 429 错误
limiter: false
image_proxy: true
port: 8080
bind_address: "0.0.0.0"
public_instance: false
ui:
static_use_hash: true
default_locale: "zh-Hans-CN"
query_in_title: true
infinite_scroll: true
center_alignment: false
default_theme: "simple"
theme_args:
simple_style: "auto"
redis:
url: "redis://redis:6379/0"
outgoing:
request_timeout: 5.0
max_request_timeout: 15.0
useragent_suffix: ""
pool_connections: 100
pool_maxsize: 20
enable_http2: true
engines:
- name: google
engine: google
shortcut: g
disabled: true
- name: bing
engine: bing
shortcut: b
disabled: false
- name: baidu
engine: baidu
shortcut: bd
disabled: false
2. 环境变量 (.env)
生成随机 Token 并写入 /opt/searxng/.env:
# 在终端执行
echo "SEARXNG_SECRET=$(openssl rand -hex 32)" > .env
echo "VALID_TOKENS=$(openssl rand -hex 32)" >> .env
# 查看生成的 Token
cat .env
三、构建 Token 认证网关
为了解决 Flask 默认拦截静态资源导致的页面样式丢失问题,我们需要编写一个特殊的 Python 网关。
1. 网关代码 (auth_gateway.py)
文件路径:/opt/searxng/auth-gateway/auth_gateway.py
#!/usr/bin/env python3
"""
SearXNG Token 认证网关 (修复静态资源 404 问题)
"""
import os
import logging
from flask import Flask, request, Response, jsonify
import requests
# ==================== 关键配置 ====================
# static_folder=None 是修复样式丢失的核心,禁用 Flask 默认静态文件处理
app = Flask(__name__, static_folder=None)
# 后端配置 (去除末尾斜杠)
SEARXNG_BACKEND = os.getenv('SEARXNG_BACKEND', 'http://searxng:8080').rstrip('/')
VALID_TOKENS = set(filter(None, os.getenv('VALID_TOKENS', '').split(',')))
WEB_PAGES = {'/', '/preferences', '/stats', '/config', '/search'}
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
def forward(path):
"""通用转发逻辑"""
target_path = path.lstrip('/')
backend_url = f"{SEARXNG_BACKEND}/{target_path}"
args = request.args.copy()
args.pop('token', None) # 移除 Token 参数
try:
headers = {k: v for k, v in request.headers if k.lower() not in ['host', 'x-api-token', 'content-length']}
resp = requests.request(
method=request.method,
url=backend_url,
headers=headers,
params=args,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
stream=True, # 关键:流式传输,保证 CSS/JS/图片完整加载
timeout=30
)
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
headers = [(k, v) for k, v in resp.raw.headers.items() if k.lower() not in excluded_headers]
return Response(resp.iter_content(chunk_size=1024*8), resp.status_code, headers)
except Exception as e:
logger.error(f"转发异常: {str(e)}")
return jsonify({'error': 'Gateway Error'}), 502
def render_login_page():
html = """
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>SearXNG Login</title></head>
<body style="display:flex;justify-content:center;align-items:center;height:100vh;background:#f0f2f5;">
<form action="/" method="get" style="background:white;padding:2rem;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);">
<h3>🔐 请输入访问令牌</h3>
<input type="password" name="token" required style="width:100%;padding:10px;margin:10px 0;">
<button type="submit" style="width:100%;padding:10px;background:#007bff;color:white;border:none;cursor:pointer;">进入</button>
</form>
</body>
</html>
"""
return Response(html, mimetype='text/html'), 401
# 万能路由入口
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST'])
@app.route('/<path:path>', methods=['GET', 'POST'])
def handle_all_requests(path):
# 1. 静态资源直接放行 (白名单机制)
if path.startswith('static/') or path in ['favicon.ico', 'opensearch.xml', 'robots.txt']:
return forward(path)
# 2. 提取 Token
token = request.args.get('token') or request.headers.get('X-API-Token') or request.cookies.get('searxng_token')
# 3. 验证 Token
if token and token in VALID_TOKENS:
resp = forward(path)
# 登录成功种 Cookie (7天有效)
if resp.status_code == 200 and 'text/html' in resp.headers.get('Content-Type', ''):
resp.set_cookie('searxng_token', token, max_age=604800, httponly=True)
return resp
# 4. 验证失败
if path == '' or path in WEB_PAGES:
return render_login_page()
return jsonify({'error': 'Unauthorized'}), 401
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8003, threaded=True)
2. Dockerfile
文件路径:/opt/searxng/auth-gateway/Dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install --no-cache-dir Flask==3.0.0 requests==2.31.0 gunicorn==21.2.0
COPY auth_gateway.py .
CMD ["gunicorn", "--bind", "0.0.0.0:8003", "--workers", "4", "--threads", "4", "--timeout", "60", "auth_gateway:app"]
四、Docker Compose 编排
编辑 /opt/searxng/docker-compose.yml:
version: "3.9"
services:
# 认证网关 (对外暴露 8003)
auth-gateway:
build: ./auth-gateway
container_name: searxng-auth-gateway
restart: unless-stopped
ports:
- "8003:8003"
environment:
- SEARXNG_BACKEND=http://searxng:8080
- VALID_TOKENS=${VALID_TOKENS}
networks:
- searxng-net
depends_on:
- searxng
# SearXNG (仅内部访问)
searxng:
image: searxng/searxng:latest
container_name: searxng
restart: unless-stopped
depends_on:
redis:
condition: service_healthy
expose:
- "8080"
volumes:
- ./searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=http://localhost:8003/
- SEARXNG_SECRET=${SEARXNG_SECRET}
networks:
- searxng-net
redis:
image: redis:7-alpine
container_name: searxng-redis
command: redis-server --save 30 1 --loglevel warning
restart: unless-stopped
volumes:
- redis-data:/data
networks:
- searxng-net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
redis-data:
networks:
searxng-net:
driver: bridge
五、启动与排错 (Troubleshooting)
1. 启动命令
cd /opt/searxng
# 必须加上 --build,否则 Python 代码修改不会生效
docker compose up -d --build
2. 常见错误解决方案
🔴 错误一:Internal Server Error (500)
ERROR: yaml.scanner.ScannerError: mapping values are not allowed here...
原因: settings.yml 文件格式错误。通常是因为直接复制了带注释的配置到 Vim 中,导致缩进错乱或混入了非法字符。
解决: 使用本文提供的纯净版配置(无注释),确保 YAML 缩进严格对齐。
🔴 错误二:Web 页面样式丢失 (404)
GET /static/themes/simple/sxng-ltr.min.css 404
原因: Flask 默认会拦截 /static 路由并在容器本地查找文件,导致请求未转发给 SearXNG 后端。
解决:
- 在 Python 代码中初始化 Flask 时添加参数:
static_folder=None。 - 手动在代码中处理
path.startswith('static/')的转发逻辑。 - 执行
docker compose up -d --build重新构建镜像。
🔴 错误三:Too Many Requests (429)
HTTP/1.1 429 Too Many Requests
原因: SearXNG 默认开启了限流。所有请求经过网关转发后,对于 SearXNG 来说来源 IP 都是网关容器的 IP,瞬间触发单 IP 限流。
解决: 在 settings.yml 中设置 server.limiter: false。
💡 提示: 部署成功后,访问
http://IP:8003即可看到 Token 登录页面。输入.env文件中的 Token 即可使用。
更多推荐

所有评论(0)