Nginx 反向代理和负载均衡入门

第一次配反代,接口通了但后台死活登不上,session 每次都丢,原因是 Set-Cookie 在代理过程中被改了。这篇从那次开始,讲 upstream、健康检查、负载策略、请求头和几个常调的超时参数。
第一次反代把我的 Cookie 错配了
我给博客上 HTTPS 之前,先做了一步基础反代。当时后端 uvicorn 跑在 127.0.0.1:8000,Nginx 监听 80 端口,把 /api/ 转给 uvicorn。看上去一切正常,文章列表接口 200。
但我后台登录功能死活登不上。密码输入、点击、接口收到、Redis 里存好 session;然后下一次请求,session 不见了。我打开 Chrome Devtools 看 Set-Cookie,发现有这条:
Set-Cookie: tiana_admin_session=...; HttpOnly; SameSite=Lax没毛病啊。但浏览器下一次请求没带上。问题在后端返回的 Cookie 之前,被 Nginx 转译了什么东西。
我在 nginx.conf 翻来翻去看了半天,原来 proxy_pass http://127.0.0.1:8000; 后面没加斜杠。意味着 location /api/ 被匹配后,原样转给后端的 /api/。我以为转成根目录 /,实际没有。后端收到的是 /api/blog/posts/list,路径对,但 uvicorn 起在 8000 上面,前面还有一层 proxy_set_header 处理过的 Host 跟实际域名不一致,导致某些 cookie 校验逻辑写错了。
行,最后通过改 proxy_set_header Host $host;,由 Nginx 把真实域名传给后端就好了。这事让我从这以后任何反代配置都开始按"最小可工作集"一步步搭,不一次到位配置太多。
反向代理的最简骨架
我现在的反代模板长这样:
server {
listen 80;
server_name blog.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name blog.example.com;
ssl_certificate /etc/nginx/ssl/blog.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/blog.key;
location /api/ {
proxy_pass http://127.0.0.1:8000;
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_read_timeout 60s;
}
}这就是最小可工作集:把 /api/ 透传到后端 + 三个标准的代理请求头。其它的缓存、超时、限流都是后续增量。
proxy_pass 末尾的斜杠非常关键。http://127.0.0.1:8000(无斜杠)会原样把 /api/blog/posts/list 转给后端;http://127.0.0.1:8000/(有斜杠)会把 /api/ 部分替换为空,剩下 /blog/posts/list。
上游多节点:upstream
我这站点初期一台服务器够用,但承接之后我可能会拆前后端或上多台。Nginx 的 upstream 块就是用来做这事的。
upstream backend_cluster {
server 10.0.0.11:8000 weight=3;
server 10.0.0.12:8000 weight=2;
server 10.0.0.13:8000 weight=1;
keepalive 32;
}
server {
listen 80;
location /api/ {
proxy_pass http://backend_cluster;
proxy_set_header Host $host;
}
}策略说明:
weight=3表示这一台承接 3/6 的请求,权重越高越常被打。机器性能不同就调 weight- 默认策略是 round robin(轮询)
ip_hash让同一客户端落到同一后端,避免缓存命中率抖动least_conn把请求给连接数最少的机器keepalive 32跟上游保持 32 个空闲连接,避免反复建连
健康检查
默认 upstream 是「nginx 看着端口在就视为可用」,实际服务挂了 Nginx 还是会发给那台。加 health_check:
upstream backend_cluster {
server 10.0.0.11:8000;
server 10.0.0.12:8000;
}
server {
location /api/ {
proxy_pass http://backend_cluster;
health_check interval=5s fails=2 passes=2 uri=/healthz;
}
}/healthz 这个路径要后端提供一个不依赖鉴权的轻量接口,返回 200 就是健康的。interval=5s 每 5 秒检查一次,连续失败 2 次摘掉,连续 2 次成功加回。商业版 Nginx 才支持 health_check,开源版要用 ngx_http_upstream_check_module,或者自己写脚本。
便宜的替代:让后端应用自己注册主动健康上报,存到一个 Lua 共享字典,Nginx 拿来判断。开源社区方案活跃,不要死磕一条路。
实际负载策略
我用 ip_hash 比较多。原因是博客后端做了 JWT 缓存,同一用户复访时打同一台机器,命中率更高。但 ip_hash 的副作用是 NAT 后面的用户都打到同一台机器,所以大流量场景用 least_conn 更稳。
我博客这种小流量无所谓,选 ip_hash 主要是简化缓存设计。
真实的请求头处理
上面的 proxy_set_header 是最小集。生产环境往往还需要:
proxy_set_header X-Request-ID $request_id;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port $server_port;X-Request-ID 用 $request_id 是 Nginx 自带的请求唯一 ID,后端日志里接住它,整个调用链就能串起来。我博客的 access_logs 里就拿这个 ID 索引入行查错。
X-Forwarded-* 给依赖原始域名信息的应用用。比如某些后端按 Host 重定向,如果没拿到原始 Host,重定向 URL 就会指向 127.0.0.1:8000。
超时与缓冲的几个常调参数
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 16k;proxy_read_timeout 是「等到后端响应的时长」,我这站详情接口冷启动偶发 5 秒,60 秒足够。proxy_buffering 媒体流/SSE 场景建议关掉。我当时踩过一次流式接口 nginx 把响应一次吐完,排查就是 buffering 在背后偷偷攒。
我现在的反代心智模型
我每次新增一个反代配置,先把骨架完整读三遍:
listen:端口对不对、TLS 证书对不对server_name:域名对不对location:路径匹配规则、URI 替换proxy_set_header:原始 Host/IP/Proto 是否传到了proxy_pass:末尾斜杠对路径的影响upstream:多节点时是否漏了health_check
然后 nginx -t,然后 nginx -s reload。之后 curl -I 看返回头、查 access_log 看请求路径是否符合预期。
反代配置踩的坑都能 curl -v 看出来,永远是几行 grep 的事。难的不是修,而是知道往哪个方向看。