向量数据库保存由业务数据生成的嵌入,载荷还可能包含客户消息、商品目录和内部文档。若 Qdrant 使用默认明文端口且未启用认证,公开实例可能被扫描发现,进而遭到数据读取或篡改。除应用层设置外,还应结合主机防火墙,并按需要配置 Fail2ban 等机制限制异常来源。
本文从头加固一个 Qdrant 部署:只向回环接口发布后端端口,以 Nginx 为 REST 和 gRPC 终止 TLS,设置强 API 密钥,使用 JWT 为集合分配访问权限,再通过 Nginx limit_req 限流。原文在 2026 年 5 月使用公网虚拟机和真实 Let’s Encrypt 证书搭建,并于 2026 年 10 月在 Ubuntu 24.04、Qdrant 1.19.2、Nginx 1.24 和 qdrant-client 1.19.1 上复测,修订了四处失效步骤。以下测量与验证结果均来自原文。
威胁模型:公开的 Qdrant 会暴露什么
如果容器把默认服务端口映射到所有接口,6333 提供无 TLS 的 REST,6334 提供无 TLS 的 gRPC。未配置 API 密钥时,未经认证的访问者可以列出集合、读取点、下载快照,也能创建或删除数据。即使有密钥,明文传输仍可能让网络路径上的观察者取得凭据与 JWT。
常见风险有三种:
- 通过 REST 直接导出。 攻击者访问服务器的
/collections,发现无保护集合后,使用 scroll 逐页取出向量与原始文本载荷。 - 明文密钥泄露。 开启 API 密钥却仍使用 HTTP,每个请求都在明文请求头中携带
api-key;截获后可被重复利用,直到密钥失效。 - 公开的 gRPC。 curl 不直接使用 gRPC,并不意味着这个端口隐蔽。任何兼容的 Qdrant 客户端都可能连接明文 6334 并读写数据。
应分层防护:后端只绑定回环,前端终止 TLS,每次数据访问都认证,再限制异常请求速率。每一层处理不同问题。
将 Qdrant 端口绑定到回环并设置强密钥
两项基础要求是:不把 Qdrant 端口直接发布到公网网卡,以及为数据访问启用真实密钥。先生成 32 字节随机密钥,保存在容器外的文件中。下面的 chown 主要适用于 UID 1000 运行的镜像;原文使用的标准镜像以 root 运行,因此这一步并非其必要条件。
sudo mkdir -p /etc/qdrant /opt/qdrant/storage /opt/qdrant/snapshots
sudo chown -R 1000:1000 /opt/qdrant
openssl rand -base64 32 | tr -d '=+/' | cut -c1-40 \
| sudo tee /etc/qdrant/api-key.secret > /dev/null
sudo chmod 600 /etc/qdrant/api-key.secret
启动容器时,关键是 -p 127.0.0.1:PORT:PORT。Docker 只在回环接口发布端口,外部主机不能直接连接 6333 或 16334。原文特别提醒,Docker Engine 28.0 之前的版本曾存在同一二层网络中的主机能访问回环发布端口的问题,应通过 docker version --format '{{.Server.Version}}' 核对版本,不要只凭绑定写法判断。
原文复测时 Ubuntu 24.04 的相关更新源提供较新的 Docker 包,使用 Docker 官方仓库安装也可以。实际操作时以自己的仓库和已安装版本为准。如果已有 Qdrant 容器,应先核对持久化目录,再有计划地替换;下面的 docker rm -f qdrant 会中断容器,新的 -v 必须指向原数据,否则看起来会像集合丢失。QDRANT__SERVICE__API_KEY 用于启用数据操作认证。
API_KEY=$(sudo cat /etc/qdrant/api-key.secret)
# Note: port 16334 for gRPC on loopback, Nginx will own external 6334
sudo docker run -d --name qdrant --restart=always \
-p 127.0.0.1:6333:6333 -p 127.0.0.1:16334:6334 \
-e QDRANT__SERVICE__API_KEY="$API_KEY" \
-e QDRANT__SERVICE__JWT_RBAC=true \
-v /opt/qdrant/storage:/qdrant/storage \
-v /opt/qdrant/snapshots:/qdrant/snapshots \
qdrant/qdrant:v1.19.2
分别验证本地回环可访问、公网网卡不可直接访问:
# Loopback responds with the version banner
curl -sS http://localhost:6333/ -H "api-key: $API_KEY"
# {"title":"qdrant - vector search engine","version":"1.19.2","commit":"016542aa5deb6c66380bb137badf73d54f742bde"}
# The host's own LAN address is refused, nothing listens there
curl -m 3 -sS -o /dev/null -w 'HTTP=%{http_code}\n' http://10.0.1.50:6333/
# curl: (7) Failed to connect to 10.0.1.50 port 6333 after 0 ms: Couldn't connect to server
# HTTP=000
将 10.0.1.50 替换为服务器自己的地址。即时返回 Couldn't connect 表明对应接口上没有监听器,而不是等待连接超时。内部 gRPC 使用 16334,是为了将公网 6334 留给 Nginx 终止 TLS;也可以选择其他未占用的回环端口。

申请 Let’s Encrypt 证书
最直接的方式是 HTTP-01:Certbot 的 Nginx 插件为匹配域名的 server 块临时加入验证路径,Let’s Encrypt 通过公网 80 端口读取令牌后签发证书。前提是域名解析到服务器公网地址,且云防火墙和主机防火墙允许相应流量。申请前按部署需要开放 80、443、6334;使用 UFW 时可运行 sudo ufw allow 80,443,6334/tcp。
先配置 DNS A 记录指向主机,再安装 Nginx 和 Certbot:
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
创建匹配域名的最小虚拟主机,供 Certbot 挂载验证路径:
sudo vim /etc/nginx/sites-available/qdrant
写入以下 80 端口配置,并把示例域名改成自己的。占位响应只用于 Certbot 修改配置之前接收请求:
server {
listen 80;
server_name qdrant.example.com;
location / { return 200 'cfg qdrant'; }
}
启用站点、移除默认站点并重新加载:
sudo ln -sf /etc/nginx/sites-available/qdrant /etc/nginx/sites-enabled/qdrant
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
运行 Certbot。--nginx 会修改虚拟主机以安装证书,--redirect 会添加 HTTP 到 HTTPS 的重定向:
sudo certbot --nginx \
-d qdrant.example.com \
--non-interactive --agree-tos --redirect \
-m admin@example.com
Certbot 会输出证书与私钥路径、有效期和续期安排。还应确认出现类似 Successfully deployed certificate for qdrant.example.com to /etc/nginx/sites-enabled/qdrant 的成功安装消息,不能只看到签发成功就假定 Nginx 已正确使用证书。后面的配置依赖这些文件实际存在。
无法开放公网 80 端口时改用 DNS-01
主机位于私网、NAT 后没有端口转发,或需要通配符证书时,可使用 DNS-01。Certbot 为常见 DNS 服务商提供插件,Ubuntu 中对应包如下:
| DNS 服务商 | 插件包 |
|---|---|
| Cloudflare | python3-certbot-dns-cloudflare |
| Route 53 | python3-certbot-dns-route53 |
| DigitalOcean | python3-certbot-dns-digitalocean |
| Google Cloud DNS | python3-certbot-dns-google |
| Linode | python3-certbot-dns-linode |
| RFC2136(BIND) | python3-certbot-dns-rfc2136 |
以下以 Cloudflare 为例,令牌需要对应 DNS 区域的 Zone:DNS:Edit 权限。DNS 插件负责验证,Nginx 插件继续负责安装,这样 Certbot 仍会生成完整虚拟主机引用的 SSL 选项文件,并在续期后重新加载 Nginx。仅运行 certonly 会跳过安装过程,若配置引用的 /etc/letsencrypt/options-ssl-nginx.conf 不存在,nginx -t 就会失败。其他服务商应替换相应插件与参数。
sudo apt install -y python3-certbot-dns-cloudflare
echo "dns_cloudflare_api_token = YOUR_TOKEN" | sudo tee /etc/letsencrypt/cf.ini > /dev/null
sudo chmod 600 /etc/letsencrypt/cf.ini
sudo certbot --authenticator dns-cloudflare --installer nginx \
--dns-cloudflare-credentials /etc/letsencrypt/cf.ini \
-d qdrant.example.com --redirect \
--non-interactive --agree-tos -m admin@example.com
该步骤只替换前面的 certbot --nginx 申请命令。仍需安装 Nginx、python3-certbot-nginx,并保留带正确 server_name 的 server 块,供安装器定位。原文实际搭建使用 HTTP-01,DNS-01 命令依据插件用法整理。示例等待 TXT 传播 10 秒,必要时增加 --dns-cloudflare-propagation-seconds。
为 REST 和 gRPC 配置 Nginx 反向代理
完整配置包含三个 server 块:80 端口重定向到 HTTPS、443 提供 REST、6334 提供 gRPC。打开 Certbot 修改过的站点文件:
sudo vim /etc/nginx/sites-available/qdrant
用下面的完整配置替换文件内容,包括 Certbot 添加的 SSL 部分。顶端的限流区域由两种协议共享,避免同一个来源仅通过切换协议获得另一份配额。
# /etc/nginx/sites-available/qdrant
limit_req_zone $binary_remote_addr zone=qdrant_rl:10m rate=5r/s;
server {
listen 80;
server_name qdrant.example.com;
server_tokens off;
return 301 https://$host$request_uri;
}
# REST over HTTPS
server {
listen 443 ssl http2;
server_name qdrant.example.com;
ssl_certificate /etc/letsencrypt/live/qdrant.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/qdrant.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
server_tokens off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
client_max_body_size 128m;
proxy_read_timeout 120s;
location / {
limit_req zone=qdrant_rl burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:6333;
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;
proxy_http_version 1.1;
}
}
# gRPC over HTTPS, separate block because grpc_pass needs end-to-end HTTP/2
server {
listen 6334 ssl http2;
server_name qdrant.example.com;
ssl_certificate /etc/letsencrypt/live/qdrant.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/qdrant.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
server_tokens off;
grpc_read_timeout 120s;
grpc_send_timeout 120s;
client_max_body_size 128m;
location / {
limit_req zone=qdrant_rl burst=50 nodelay;
limit_req_status 429;
# Internal Qdrant gRPC on the loopback. Note grpc:// (cleartext to backend).
grpc_pass grpc://127.0.0.1:16334;
grpc_set_header X-Real-IP $remote_addr;
}
}
注意三个细节。首先,gRPC 使用 grpc_pass,不能用普通 HTTP 的 proxy_pass 替代。其次,本机回环后端使用 grpc://,TLS 在 Nginx 边界终止,因此不是 grpcs://。最后,示例的突发额度不同:REST 为 20,gRPC 为 50,后者考虑了服务调用常见的瞬时并发。
Ubuntu 24.04 的 Nginx 1.24 使用上述 listen ... ssl http2 语法。Nginx 1.25.1 起提供独立的 http2 指令,新版本使用旧写法可能出现弃用警告。可把监听改为 listen 443 ssl 与 listen 6334 ssl,再在各 server 块内加入 http2 on。不要把这个新语法直接复制到 Nginx 1.24,它会报告 unknown directive "http2"。
检查并加载配置:
sudo nginx -t
sudo systemctl reload nginx
sudo ss -tlnp | grep -E ':80|:443|:6334'
# nginx listens on 0.0.0.0:80, 0.0.0.0:443, 0.0.0.0:6334
Nginx 能监听 0.0.0.0:6334,是因为前面已把 Qdrant 内部 gRPC 发布到 16334。如果忘记,Nginx 将因端口占用而无法加载新配置。原文环境中 reload 命令仍可能返回 0,旧配置继续运行,错误只记在 /var/log/nginx/error.log;此时直接 restart 还可能导致整个服务启动失败。修改端口映射后应检查日志和 ss -tlnp,不能只看命令退出码。
验证整条访问链路
使用 curl 与 openssl 逐层验证:
API_KEY=$(sudo cat /etc/qdrant/api-key.secret)
# 1. Plain HTTP redirects to HTTPS
curl -sS -o /dev/null -w '%{http_code} -> %{redirect_url}\n' \
http://qdrant.example.com/
# 301 -> https://qdrant.example.com/
# 2. HTTPS rejects requests with no api-key
curl -sS -o /dev/null -w 'HTTP=%{http_code}\n' \
https://qdrant.example.com/collections
# HTTP=401
# 3. HTTPS accepts the same request with a valid api-key
curl -sS -o /dev/null -w 'HTTP=%{http_code}\n' \
https://qdrant.example.com/collections -H "api-key: $API_KEY"
# HTTP=200
# 4. The version banner comes through the proxy (/ is public, no key needed)
curl -sS https://qdrant.example.com/
# {"title":"qdrant - vector search engine","version":"1.19.2","commit":"016542aa..."}
检查 TLS 握手是否成功验证证书,以及协商出的协议。这里使用 -brief,因为习惯性的 echo | openssl s_client 再 grep Protocol,在 TLS 1.3 下可能没有输出:服务器的握手后会话票据未及时到达,输入流已经结束,客户端便没有打印预期摘要。原文 10 月复测就遇到了这种情况。
echo | openssl s_client -brief -connect qdrant.example.com:443 \
-servername qdrant.example.com
# CONNECTION ESTABLISHED
# Protocol version: TLSv1.3
# Ciphersuite: TLS_AES_256_GCM_SHA384
# Peer certificate: CN = qdrant.example.com
# Hash used: SHA256
# Signature type: ECDSA
# Verification: OK
# Server Temp Key: X25519, 253 bits
# DONE
5 月的 Qdrant 1.18.1 测试截图使用旧写法,恰好收到了会话摘要,同时显示 Nginx 加入的 HSTS 等安全响应头:

Web UI 通过配置好的域名和 /dashboard 路径使用 HTTPS,其数据请求同样需要密钥。以下截图也来自原文 5 月环境:

Python gRPC 客户端需要 https=True、公网域名以及容易遗漏的 port=443。客户端会先通过 REST 检查服务端兼容性,若不指定 REST 端口,仍可能访问已经关闭的 6333,导致每次启动出现 Failed to obtain server version. Unable to check client-server compatibility.。认证密钥通过 HTTP/2 元数据传递。
from qdrant_client import QdrantClient
import os
client = QdrantClient(
host="qdrant.example.com",
port=443,
grpc_port=6334,
prefer_grpc=True,
https=True,
api_key=os.environ["QDRANT_API_KEY"],
)
print(client.get_collections())
Ubuntu 不直接提供 qdrant-client 系统包,部分 24.04 云镜像也没有预装 pip 与 venv。为客户端建立独立虚拟环境,把上面的代码保存为 grpc_check.py,再通过环境变量传入密钥:
sudo apt install -y python3-venv
python3 -m venv ~/qdrant-venv
~/qdrant-venv/bin/pip install qdrant-client
export QDRANT_API_KEY="$API_KEY"
~/qdrant-venv/bin/python grpc_check.py
尚未创建集合时,预期返回空列表,且没有兼容性检查警告:
collections=[]
原文同机客户端的第一次 TLS gRPC 查询包含握手,共耗时 21.4 毫秒;随后 50 次查询复用连接,中位数为 1.9 毫秒,p95 为 2.9 毫秒。若客户端不信任证书,例如缺少私有 CA,会返回 StatusCode.UNAVAILABLE,详情含 CERTIFICATE_VERIFY_FAILED。本地成功而远程失败时,证书信任链是首要检查项之一。
使用 JWT 为不同集合分配访问令牌
主 API 密钥供管理员使用。应用、构建流水线与值班面板通常只需要部分集合权限。Qdrant 的 JWT-RBAC 使用 HMAC 签名令牌,通过 access 声明限定可访问集合及读写能力。
容器已设置 QDRANT__SERVICE__JWT_RBAC=true 时,可以使用主密钥签发 HS256 JWT。签名材料就是主 API 密钥字符串,必须只在受信服务端保管与使用。以下令牌限定到 jwt_demo,先用主密钥创建这个 4 维测试集合:
curl -sS -X PUT https://qdrant.example.com/collections/jwt_demo \
-H "api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"vectors":{"size":4,"distance":"Cosine"}}'
# {"result":true,"status":"ok","time":2.976137414}
如果跳过创建,后面的检查可能先收到集合不存在的 404,难以判断权限是否按预期拒绝写入。使用 Python 的 jwt 模块签发令牌;Ubuntu 包名为 python3-jwt,原文 24.04 云镜像已安装。这样可避免向系统 Python 直接 pip 安装时遇到 externally-managed-environment。密钥文件仅 root 可读,因此示例通过 sudo 运行,并把令牌保存在 Shell 变量中:
sudo apt install -y python3-jwt
RO_TOKEN=$(sudo python3 -c 'import jwt; k=open("/etc/qdrant/api-key.secret").read().strip(); print(jwt.encode({"access":[{"collection":"jwt_demo","access":"r"}]}, k, algorithm="HS256"))')
RW_TOKEN=$(sudo python3 -c 'import jwt; k=open("/etc/qdrant/api-key.secret").read().strip(); print(jwt.encode({"access":[{"collection":"jwt_demo","access":"rw"}]}, k, algorithm="HS256"))')
echo "RO: ${RO_TOKEN:0:24}... RW: ${RW_TOKEN:0:24}..."
令牌和主密钥一样,放入 api-key 请求头。Qdrant 验证签名后应用权限范围。需要实际验证拒绝行为,不能只检查 JWT 看起来正确。
# Read with the RO token (must succeed)
curl -sS -o /dev/null -w 'GET (RO) HTTP=%{http_code}\n' \
https://qdrant.example.com/collections/jwt_demo \
-H "api-key: $RO_TOKEN"
# GET (RO) HTTP=200
# Write with the RO token (must be blocked)
curl -sS -o /dev/null -w 'PUT (RO) HTTP=%{http_code}\n' \
-X PUT https://qdrant.example.com/collections/jwt_demo/points \
-H "api-key: $RO_TOKEN" -H "Content-Type: application/json" \
-d '{"points":[{"id":1,"vector":[0.1,0.2,0.3,0.4]}]}'
# PUT (RO) HTTP=403
# Write with the RW token (must succeed)
curl -sS -o /dev/null -w 'PUT (RW) HTTP=%{http_code}\n' \
-X PUT "https://qdrant.example.com/collections/jwt_demo/points?wait=true" \
-H "api-key: $RW_TOKEN" -H "Content-Type: application/json" \
-d '{"points":[{"id":1,"vector":[0.1,0.2,0.3,0.4]}]}'
# PUT (RW) HTTP=200
只读令牌写入得到 403,响应为 Forbidden: Write access to collection jwt_demo is required,才能证明这一条权限限制生效。访问其他集合应提示缺少该集合权限;集合级读写令牌也不能创建或删除集合,因为那需要全局权限。列出集合时仅返回令牌可见集合。gRPC 的相应拒绝以 StatusCode.PERMISSION_DENIED 返回。
CI 作业、定时采集器等临时任务应在 exp 中设置 Unix 过期时间。原文测试中,过期令牌在允许的 30 秒偏差后得到 HTTP 403 与 ExpiredSignature。更换主密钥会使旧签名失效:原文重建容器后,旧主密钥返回 401,旧令牌得到 InvalidSignature。需要平滑轮换时,可评估 alt_api_key 的并行接受能力,但旧 JWT 的签发与重发仍需按实际版本验证和安排。
还有一项危险的默认行为:不含 access 声明的有效 JWT 可能被视为拥有完整管理权限。 原文用只有 "sub": "ci-bot" 的令牌验证,确实能删除集合。因此每次签发都应明确写入 access,不能把省略字段误当成最小权限。
使用 limit_req 限流
Nginx 的 limit_req_zone 按来源 IP 计数。示例速率为 5 r/s、REST 的 burst 为 20:在空闲状态下可立即处理正常额度加 20 个突发请求,此后按每秒 5 个请求的速率恢复。超出额度时,配合 limit_req_status 429 与 nodelay 立即返回 429;去掉 nodelay 则会按配置延迟排队。
快速连续请求 30 次,观察限制:
for i in $(seq 1 30); do
curl -sS -o /dev/null -w '%{http_code} ' \
https://qdrant.example.com/collections \
-H "api-key: $API_KEY"
done
echo
# 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
# 200 200 200 200 200 429 429 429 200 429
原文 10 月测试中,前 25 次成功,因为循环期间额度也在恢复;第 26 至 28 次为 429,第 29 次恰好等到额度恢复,第 30 次又被拒绝。具体数量随往返耗时变化。日志会出现类似 limiting requests, excess: 20.045 by zone "qdrant_rl" 的记录,gRPC 客户端可能收到 StatusCode.UNAVAILABLE 与 Received http2 header with status: 429。
下面截图来自 5 月环境,当时使用 Python 脚本验证令牌,并在首次 429 前通过了 27 个请求。速率应根据用途设定:原文以仪表板 5 r/s、服务调用 50 r/s 为起点,burst 则与合法调用方的预期峰值并发相匹配。

限流不能代替认证。取得有效密钥的人仍可能持续读取数据,限流只是减慢访问并提供可监测信号。应对同一 IP 的持续 429 峰值设置日志告警。
同时配置云防火墙
Nginx 负责 TLS,云网络防火墙负责阻止不需要的端口流量到达主机。可以在安全组、云防火墙规则或安全列表中开放业务所需的 80、443、6334,将 SSH 22 限定到管理网络。下面 AWS 示例中的 203.0.113.0/24 代表办公室或 VPN 网段:
# Allow inbound to security group cfg-prod-qdrant-sg
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d4e5f67890 --protocol tcp \
--port 22 --cidr 203.0.113.0/24 # SSH from admins only
for PORT in 80 443 6334; do
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d4e5f67890 --protocol tcp \
--port $PORT --cidr 0.0.0.0/0
done
新增受限 SSH 规则不会自动移除创建主机时的 0.0.0.0/0 开放规则。先确认当前出口地址处于允许范围内,保留现有连接,再撤销宽泛规则:
aws ec2 revoke-security-group-ingress \
--group-id sg-0a1b2c3d4e5f67890 --protocol tcp \
--port 22 --cidr 0.0.0.0/0
关闭旧会话前先成功建立第二个 SSH 连接,避免网段填写错误导致失联。云层关闭 6333 还能形成另一道边界:即使 Docker 发布配置出错,也不会直接从公网连到该端口。
容易遗漏的五个细节
- Docker 要完整指定回环地址。
-p 6333:6333发布到所有接口;应使用-p 127.0.0.1:6333:6333,再用ss -tlnp检查,同时注意前述旧版 Docker 行为。 - Nginx 与 Qdrant 不能同时占用 6334。 即使后端只监听回环,也可能与 Nginx 的通配监听冲突。把后端发布到 16334,并在 reload 后查看错误日志与实际监听器。
- gRPC 的前后端协议要正确。 Nginx 1.24 的监听需含
ssl http2,上游使用grpc_pass。错误使用proxy_pass可能出现upstream sent no valid HTTP/1.0 header;缺少 HTTP/2 协商可能得到TLSV1_ALERT_NO_APPLICATION_PROTOCOL,不一定是证书本身有问题。 - JWT-RBAC 既需要开关,也需要正确令牌。 仅启用环境变量仍只是使用主密钥。为非管理员签发明确带
access的令牌,并安排轮换;缺少该声明可能获得管理权限。 - HTTP-01 续期仍要访问 80 端口。 首次申请后关闭验证通道,会导致后续续期失败。若不能持续提供 HTTP 验证,应改用 DNS-01,并检查实际证书有效期与续期策略。
续期与可观测性
检查 certbot.timer 是否启用,并用 dry-run 确认 HTTP-01 或 DNS-01 的验证条件仍满足。具体定时和续期间隔应以本机 timer 与证书策略为准:
systemctl status certbot.timer | head -4
sudo certbot renew --dry-run
在可观测性方面,先从 Nginx 访问日志观察 401 与 429:
sudo tail -F /var/log/nginx/access.log \
| awk '$9 == 401 || $9 == 429 {print strftime("%H:%M:%S"), $1, $7, $9}'
来自同一 IP 的 401 峰值可能是密钥尝试,也可能是客户端配置错误;429 峰值可能来自异常或过量调用,包括自己的服务。可以接入已有日志平台,对持续异常设置阈值告警。
但该过滤主要覆盖 REST。gRPC 的应用状态位于响应尾部,错误密钥调用在 Nginx 中也可能记为 HTTP 200,因此不能只按 HTTP 401 检测 6334 的认证失败。Qdrant 审计日志更适合观察这些事件。原文给出的设置为 QDRANT__AUDIT__ENABLED=true 与 QDRANT__AUDIT__TRUST_FORWARDED_HEADERS=true,并要求 Nginx 用真实连接地址覆盖转发头:gRPC 配置加入 grpc_set_header X-Forwarded-For $remote_addr; 和 grpc_set_header Forwarded "";,REST 使用相应的 proxy_set_header。
这里要覆盖而非盲目追加客户端提供的头,以免把伪造地址写入日志。原文指出 Qdrant 优先读取 Forwarded,其次 X-Forwarded-For,而不读取此处示例的 X-Real-IP;若配置缺失,日志可能只看到 Docker 网桥地址。原文没有对本次环境的审计日志配置进行实测,部署时应先核对当前版本支持情况,并将代理限定为受信来源。
这套加固针对明文窃听、凭据复用、未经认证的数据导出、权限过大和异常请求等风险。每次更改部署后,都应重新执行验证章节中的检查,确认数据访问、加密与拒绝行为仍符合预期。










