<!DOCTYPE html>
反向代理是 Nginx 最核心的能力:对外统一入口、对内分发请求。很多同学会配一个最简单的 proxy_pass,但一旦遇到 WebSocket 连接闪断、多后端负载不均、TCP 四层转发、502/504 报错,就不知道从何下手。本文按「HTTP 反代 → 负载均衡 → WebSocket → 四层代理 → 排错」的顺序进阶,全部配置可直接复制修改使用。
一、反向代理基础:别漏了转发头
最基础的反向代理,把 80 端口的请求转发给本机 8080 的 Java/Go 应用:
server {
listen 80;
server_name app.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;
}
}
三个必须懂的细节:
# 1. proxy_pass 末尾【不带】路径 vs【带】路径,行为完全不同
proxy_pass http://127.0.0.1:8080; # 保留原始 URI:/api/user → /api/user
proxy_pass http://127.0.0.1:8080/; # 替换 URI:/api/user → /user(常用于前后端分离)
proxy_pass http://127.0.0.1:8080/v2; # 前缀替换:/api/user → /v2/user
# 2. 后端拿到的是 Nginx 的地址,不是用户地址!必须用 X-Real-IP 传递真实 IP
# 3. 应用要开启对 X-Forwarded-For 的信任,否则获取的客户端 IP 仍是内网地址
最容易踩坑:location /api/ { proxy_pass http://backend; } 时,如果后端路由是 /api/xxx,必须保证 proxy_pass 不带尾部路径;反之带尾路径会把 /api 前缀剥掉。前后端联调出现 404 时,90% 是这里的问题。
二、负载均衡:upstream 的正确打开方式
多台后端时,用 upstream 定义后端组:
upstream backend_servers {
# 默认轮询(round-robin),按请求数轮流分发
server 10.0.0.11:8080 weight=3; # 权重 3:更强机器分更多流量
server 10.0.0.12:8080 weight=1;
server 10.0.0.13:8080 max_fails=3 fail_timeout=30s; # 30 秒内失败 3 次则摘除
keepalive 32; # 长连接池,减少后端建连开销
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend_servers;
proxy_http_version 1.1; # keepalive 必须配 1.1
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_next_upstream error timeout http_502 http_503; # 故障自动切换
}
}
四种负载均衡策略对比:
| 策略 | 配置写法 | 适用场景 |
|---|---|---|
| 轮询 | 默认 | 后端性能相近 |
| 加权轮询 | server IP weight=3 | 后端性能有差异 |
| IP 哈希 | upstream 内加 ip_hash; | 需要会话保持(无共享 session) |
| 最少连接 | upstream 内加 least_conn; | 请求耗时差异大、长连接多 |
# 会话保持示例(基于客户端 IP 哈希)
upstream session_backend {
ip_hash;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
三、WebSocket 反向代理:解决「连上就断」
WebSocket 建连时先发 HTTP 请求,携带 Upgrade: websocket 和 Connection: Upgrade 头。Nginx 默认不会转发这两个头,必须显式配置:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream ws_backend {
server 127.0.0.1:9501; # 如 Swoole / Node.js WebSocket 服务
}
server {
listen 80;
server_name ws.example.com;
location /ws {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket 是长连接,必须调大超时,否则 60s 后自动断开
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
排错要点:
# 1. 忘配 Upgrade/Connection 头 → 客户端报 400 / 连接立即关闭
# 2. 忘了调 proxy_read_timeout → 连接在默认 60s 被 Nginx 掐断
# 3. 前端 wss 域名证书过期 → 浏览器控制台报 SSL 错误,与 Nginx 无关
# 4. 验证后端真实连接数(排除 Nginx 层问题)
ss -tlnp | grep 9501
四、四层代理:stream 模块转发 TCP/UDP
除了 HTTP,Nginx 还能转发 MySQL、Redis、SSH 等 TCP 流量,靠 stream 模块:
# 确认编译了 stream 模块
nginx -V 2>&1 | grep -- --with-stream
# 在 nginx.conf 顶部加载
stream {
upstream mysql_group {
server 10.0.0.21:3306;
server 10.0.0.22:3306;
}
server {
listen 3306;
proxy_pass mysql_group;
proxy_timeout 300s;
}
}
注意:stream 和 http 是平级块,不能放在 http 块内。Debian/Ubuntu 下需要把 stream 配置独立到 /etc/nginx/conf.d/stream.conf 或使用 nginx-extras 包。
# 测试连接(客户端视角)
mysql -h 你的NginxIP -P 3306 -u root -p
# 抓包确认四层转发
tcpdump -i eth0 port 3306 -nn
五、HTTPS 与后端加密回源
前端 HTTPS、后端 HTTP 是最常见组合;后端也是 HTTPS 时需要额外处理证书校验:
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass https://backend_servers; # 后端走 HTTPS
# 后端证书是自签/不匹配时,关闭校验(仅内网可信环境)
proxy_ssl_verify off;
# 多域名后端时需要 SNI
proxy_ssl_server_name on;
proxy_ssl_name backend.internal.example.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
六、高频排错:502 / 504 / 404
502 Bad Gateway —— 上游连不上
# 1. 后端真的没起来?
ss -tlnp | grep 8080
systemctl status 你的应用
# 2. 后端起来了但 socket 文件路径不对(PHP-FPM 常见)
# location ~ \.php$ { fastcgi_pass unix:/run/php/php8.2-fpm.sock; }
ls -l /run/php/php8.2-fpm.sock
# 3. 后端起在 127.0.0.1 上,但 Nginx 配的是内网 IP
ss -tlnp | grep 8080 # 确认 Listen 地址与 proxy_pass 一致
# 4. 看 Nginx 错误日志(最有价值的信息源)
tail -f /var/log/nginx/error.log
504 Gateway Timeout —— 上游响应太慢
# 默认 60 秒超时,大数据量接口/慢 SQL 会触发
proxy_connect_timeout 10s; # 建立连接超时
proxy_send_timeout 60s; # 发送请求超时
proxy_read_timeout 120s; # 读取响应超时(最常需要调大)
# 如果后端本身 5 分钟才能出结果,要同时检查后端自身超时设置
# 例:PHP-FPM 的 request_terminate_timeout、Python 的请求超时
404 —— 多半是 proxy_pass 路径问题
# 现象:直接访问后端正常,走 Nginx 就 404
# 排查:把 location 与 proxy_pass 的路径关系画出来
location /api/ { proxy_pass http://backend:8080; } # /api/user → /api/user ✅
location /api/ { proxy_pass http://backend:8080/; } # /api/user → /user 可能404
location /api/ { proxy_pass http://backend:8080/v1; } # /api/user → /v1user 路径拼接错误 ❌
# 规律:proxy_pass 不带路径 → URI 原样转发;带路径 → 前缀替换
# 想替换前缀并保留后缀,写法:proxy_pass http://backend:8080/v1/; (注意结尾斜杠)
七、性能与安全建议
# 1. 打开 upstream 长连接池(http 1.1 + keepalive)
proxy_http_version 1.1;
proxy_set_header Connection "";
upstream 内 keepalive 32;
# 2. 限制单 IP 并发,防被刷
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend_servers;
}
# 3. 隐藏后端真实信息
proxy_hide_header X-Powered-By;
server_tokens off;
# 4. 大文件上传时调大 client_max_body_size(默认仅 1M!)
client_max_body_size 100m;
# 5. 健康检查:后端故障时快速摘除
upstream backend_servers {
server 10.0.0.11:8080 max_fails=2 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=2 fail_timeout=10s;
}
# 修改配置后务必先测语法再重载
nginx -t && nginx -s reload
八、总结
反向代理进阶的核心就三件事:转发头传对(真实 IP、协议、URI)、超时与连接池调好(WebSocket、慢接口、长连接)、排错按日志走(error.log + ss 双确认)。记住一条铁律:改完配置永远先 nginx -t 再 reload。把本文的负载均衡、WebSocket、四层代理三段配置按需组合,一个高可用的网关就成型了。
如果你在实战中遇到 WebSocket 频繁断开、负载不均或奇怪的 5xx,欢迎评论区留言,带上你的 server 配置片段一起分析。


