nginx-review
name: nginx-review
description: Nginx 配置审查与反向代理调优。当要审一份 nginx.conf、配置 HTTPS 和证书、做反向代理和负载均衡、处理 502 504、优化静态资源缓存、或者流式响应被缓冲住时使用。触发词:nginx、反向代理、502、504、Bad Gateway、Gateway Timeout、证书、HTTPS、SSL、gzip、缓存头、限流、跨域、CORS、SSE 不流式、WebSocket 断开。不负责应用层性能(走 web-vitals-cn)和服务器整体排查(走 linux-incident-triage)。
Nginx 配置审查
Nginx 的坑集中在四处:代理头没传对、超时没配、缓冲把流式毁了、缓存策略让发版不生效。审配置按这四类走一遍,比逐行读快得多。
一、反向代理的必备头
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host ;
proxy_set_header X-Real-IP ;
proxy_set_header X-Forwarded-For ;
proxy_set_header X-Forwarded-Proto ;
proxy_set_header X-Forwarded-Host ;
}
逐条说明:
proxy_http_version 1.1必须写。默认是 1.0,不支持长连接,也会让 WebSocket 和分块传输出问题。Host不传的话后端拿到的是 upstream 的地址,重定向和多租户路由全乱。X-Forwarded-Proto不传的话,后端以为是 HTTP,生成的绝对 URL 会是http://,在 HTTPS 页面上触发混合内容拦截。这是「登录后跳转变成 http」这类怪问题的常见原因。X-Forwarded-For用而不是,才能保留完整链路。
注意:后端信任这些头的前提是 Nginx 是唯一入口。如果后端端口能被直接访问,攻击者可以伪造 X-Forwarded-For 绕过 IP 限制。后端端口一定要只绑 127.0.0.1。
二、超时
默认值在生产上几乎总是错的。
proxy_connect_timeout 5s; # 连不上后端就快速失败
proxy_send_timeout 60s;
proxy_read_timeout 60s; # 后端处理慢的接口要单独调大
send_timeout 60s;
keepalive_timeout 65s;
proxy_connect_timeout要短(几秒)。后端挂了就该快速失败,不要让请求堆积。proxy_read_timeout决定 504。 长耗时接口(导出、大模型流式生成)要单独 location 调大,别为了一个接口把全局调到 600 秒。keepalive_timeout要大于上游负载均衡的空闲超时,否则会出现随机的连接重置。
配 upstream 长连接能显著降低延迟:
upstream app {
server 127.0.0.1:8080;
keepalive 32;
}
配了 keepalive 就必须同时有 proxy_http_version 1.1 和 proxy_set_header Connection "";,否则不生效。
三、流式响应(SSE 和大模型接口)
这是现在最高频的坑:接口本身是流式的,经过 Nginx 之后变成一次性返回。原因是 Nginx 默认会缓冲响应。
location /v1/chat/completions {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # 关键:关掉响应缓冲
proxy_cache off;
chunked_transfer_encoding on;
proxy_read_timeout 600s; # 长生成要给足时间
gzip off; # gzip 也会攒缓冲区
}
四行缺一不可。只关 proxy_buffering 但没关 gzip,仍然会被攒住。
后端也要配合发 X-Accel-Buffering: no 响应头,双保险。
WebSocket 另外要加:
proxy_set_header Upgrade ;
proxy_set_header Connection "upgrade";
四、静态资源与缓存
# 带内容哈希的资源:长缓存
location ~* \.(js|css|woff2|png|jpg|svg)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# HTML 入口:绝不缓存
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1;
}
HTML 被缓存是发版不生效的头号原因。 用户刷新拿到旧 HTML,旧 HTML 引用的旧资源哈希已经被清理,页面直接白屏。
add_header 有个坑:同一层级只要出现一个 add_header,父级的所有 add_header 都会失效。要么全写在同一层,要么用 always 参数并注意继承规则。
压缩:
gzip on;
gzip_types text/plain text/css application/json application/javascript
application/xml image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 5;
有 Brotli 模块的话优先 Brotli,压缩率更好。图片和字体(woff2)已经压过了,不要再 gzip。
五、HTTPS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000" always;
- 关掉 TLS 1.0/1.1,但要确认没有老旧客户端依赖(国内部分老安卓)。
- 证书链要完整。 只放叶子证书不放中间证书,浏览器能过但很多命令行客户端会报错。用
openssl s_client -connect host:443 -showcerts验证。 - HSTS 上线要谨慎,一旦下发浏览器会强制 HTTPS,配错了很难回退。先用短的 max-age 试。
- 证书续期要自动化并配到期告警。证书过期是最没面子的故障。
六、502 和 504 的区分
| 错误 | 含义 | 查什么 |
|---|---|---|
| 502 Bad Gateway | 连不上后端,或后端返回了无效响应 | 后端进程活着吗、端口对不对、后端是不是崩了 |
| 504 Gateway Timeout | 连上了但等超时 | 后端处理太慢,或 proxy_read_timeout 太小 |
| 499 | 客户端主动断开 | 通常是前端超时先放弃了,看前端超时设置 |
排查顺序:
nginx -t # 配置语法
tail -f /var/log/nginx/error.log # 真正的原因都在这
curl -sv http://127.0.0.1:8080/healthz # 绕过 Nginx 直连后端
ss -lntp | grep 8080
error.log 里的 connect() failed (111: Connection refused) 是后端没起;upstream timed out 是后端太慢;no live upstreams 是所有后端都被标记为失败。
七、限流与防护
limit_req_zone zone=perip:10m rate=10r/s;
limit_conn_zone zone=conn:10m;
location /api/ {
limit_req zone=perip burst=20 nodelay;
limit_conn conn 10;
}
- 先用
limit_req_status 429让被限流的返回 429 而不是默认的 503,语义更准。 - 在 CDN 或多层代理后面,
拿到的是代理 IP,要配real_ip_header和set_real_ip_from才能拿到真实客户端 IP。 set_real_ip_from只能填可信代理的网段,填0.0.0.0/0等于允许任何人伪造来源 IP。
八、审查清单
-
proxy_http_version 1.1已设置 - 四个转发头齐全,尤其是
X-Forwarded-Proto - 后端端口只绑
127.0.0.1,不能被外部直连 - 超时值针对业务调整过,长耗时接口单独配置
- 流式接口关掉了 buffering、cache 和 gzip
- HTML 不缓存,带哈希的静态资源长缓存
- 证书链完整,续期自动化且有到期告警
-
error.log和access_log配了 logrotate -
nginx -t通过,且用nginx -s reload而不是 restart 生效 - 真实 IP 的信任网段配置正确