nginx 反向代理:几个必须搞清楚的细节
反向代理的配置写起来只有几行,但细节几乎全在「差一个斜杠」「头没传」这种地方。 这篇记的是我自己反复踩到的几处,都能用现象反推。
一、最小可用配置
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 的匹配顺序
不是代码里谁写在前面谁生效,而是有一套固定优先级:
location = /path精确匹配,命中就停。location ^~ /prefix/前缀匹配,命中后不再查正则。location ~ 正则/~* 正则按出现顺序,第一个命中就停。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 调读超时。