← 全部笔记

nginx 反向代理:几个必须搞清楚的细节

2026-10-25 系统与运维 约 9 分钟

反向代理的配置写起来只有几行,但细节几乎全在「差一个斜杠」「头没传」这种地方。 这篇记的是我自己反复踩到的几处,都能用现象反推。

一、最小可用配置

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_set_header 不是可选项。nginx 转发时默认会把 Host 改成 upstream 的名字(这里是 127.0.0.1:8080),后端如果按 Host 分流或生成绝对链接, 拿到的就是错的。

二、proxy_pass 结尾那个斜杠

这是最容易出错的一处,规则可以概括成两句话:

  • proxy_pass 后面不带 URI(没有路径部分)→ 原样转发完整请求路径。
  • proxy_pass 后面带 URI → 用这个 URI 替换 location 匹配到的那一段前缀。
# 请求 /api/users,后端收到 /api/users
location /api/ { proxy_pass http://127.0.0.1:8080; }

# 请求 /api/users,后端收到 /users   (/api/ 被替换成 /)
location /api/ { proxy_pass http://127.0.0.1:8080/; }

只差一个字符,后端就可能全 404。用正则的 location 时规则更严: proxy_pass 后面不允许带 URI,要改写路径得用 rewrite。

三、location 的匹配顺序

不是代码里谁写在前面谁生效,而是有一套固定优先级:

  1. location = /path 精确匹配,命中就停。
  2. location ^~ /prefix/ 前缀匹配,命中后不再查正则。
  3. location ~ 正则 / ~* 正则 按出现顺序,第一个命中就停。
  4. location /prefix/ 普通前缀,记住最长的那条,等正则全部失败后才用。

常见坑:写了 location ~ \.php$ 之类的正则,结果把本该走 ^~ /static/ 的请求也抢走了。想「这段路径不要碰正则」,就得用 ^~ 而不是普通前缀。

四、413 与 504:两个高频报错

413 Request Entity Too Large

client_max_body_size 默认只有 1m。 上传图片、导入数据、提交长表单超过这个值,nginx 直接拒掉,请求根本到不了后端。

client_max_body_size 20m;   # 可以放在 http / server / location 任一层

注意后端(比如 PHP 的 upload_max_filesize、Spring 的 max-file-size) 往往也有自己的上限,两层都要看,否则改完 nginx 还是失败。

504 Gateway Timeout

proxy_read_timeout 默认 60 秒。后端处理超过 60 秒没返回任何数据, nginx 就断开并回 504。导出报表、跑批这类接口经常撞上。

location /api/export {
    proxy_pass http://127.0.0.1:8080;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
}

区分一下:504 是「等了太久」,502 是「后端连不上或直接崩了」。 看到 502 先去查后端进程在不在、端口通不通,别调超时。

五、upstream 长连接

默认情况下 nginx 每转发一个请求就新建一条到后端的 TCP 连接,高并发时 TIME_WAIT 会堆起来。 配好 keepalive 能明显降下来:

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;              # 每个 worker 保留的空闲连接数
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;        # keepalive 需要 1.1
        proxy_set_header Connection ""; # 清掉 close,否则复用不了
    }
}

这三行必须成套出现。少 proxy_http_version 1.1 或留着 Connection: close,长连接都不会生效——配置看起来没报错,但连接数一点没降。

六、WebSocket 要额外的两行

location /ws {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;   # 长连接别被默认 60s 掐断
}

现象很好认:连上几秒就断、后端日志里看不到正常关闭,多半就是超时把连接掐了。

七、改完怎么验证

nginx -t                      # 语法检查
nginx -T | grep -n "proxy_pass"   # 确认实际加载的配置(含 include 进来的)
systemctl reload nginx

nginx -T 比 -t 有用得多:它把 include 的文件全部展开输出, 能确认你改的那份到底有没有被加载——尤其当同一个指令出现在多个文件里的时候。


一句话总结:斜杠决定路径怎么拼,优先级决定谁先匹配,转发头决定后端认不认得出真实客户端;413 调 body 大小,504 调读超时。