nginx-review

Category: DevOps Risk: Medium risk niuwoai/skills CC-BY-4.0
shell_executionnetwork_access

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.1proxy_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_headerset_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.logaccess_log 配了 logrotate
  • nginx -t 通过,且nginx -s reload 而不是 restart 生效
  • 真实 IP 的信任网段配置正确