在部署项目时会遇到这样的问题:
- 只有一个 80 端口可用,但公司系统有多套,这就会导致外部访问时需要域名+端口的方式,要记住各种端口是件很痛苦的事情。有没有办法让所有系统都能通过 80 端口访问呢?
- 前后端分离时,前端调后端接口总是报 CORS 跨域错误
- 想把内网的几台服务器隐藏起来,只暴露一个出口给公网
- TCP 端口转发(如 MySQL、Redis)需要四层代理
这时候,你需要一位强大的"中间人"—— Nginx 反向代理。
它是 Nginx 使用率最高的功能,没有之一。今天我们将彻底拆解 proxy_pass 指令,带你打通流量转发的"任督二脉"。
一、基础实战:配置你的第一个反向代理
场景:Node.js 应用代理
假设你有一个 Node.js 应用跑在 localhost:3000,现在想通过域名 api.example.com 访问它。
server { listen 80; server_name api.example.com; location / { proxy_pass <http://127.0.0.1:3000>; 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 转发请求时,默认会"丢掉"原始请求的一些头信息。如果不手动透传:
- 后端拿不到用户的真实 IP(
X-Real-IP),日志里全是内网 IP,做风控或限流时会失效 - 后端不知道用户用的是 HTTP 还是 HTTPS(
X-Forwarded-Proto)
重载配置
二、 经典问题:proxy_pass 后面加不加斜杠 /?
这是 Nginx 配置中最大的坑,90% 的人都踩过。
测试场景
假设请求地址是:http://api.example.com/api/user
写法 A(不带斜杠)
location /api/ { proxy_pass <http://127.0.0.1:3000>;}
结果: 后端收到的路径是 /api/user(路径透传,原样转发)
写法 B(带斜杠)
location /api/ { proxy_pass <http://127.0.0.1:3000/>;}
结果: 后端收到的路径是 /user(路径替换,把 /api/ 切掉了)
写法 C(带路径 + 斜杠)
location /api/ { proxy_pass <http://127.0.0.1:3000/backend/>;}
结果: 后端收到的路径是 /backend/user(路径替换并拼接)
结论
| | |
|---|
proxy_pass <http://127.0.0.1:3000>; | | |
proxy_pass <http://127.0.0.1:3000/; | | |
proxy_pass <http://127.0.0.1:3000/backend/; | | |
记忆口诀:
三、 生产场景 A:前后端分离部署
需求
- 后端 API:
www.example.com/api
配置
server { listen 80; server_name www.example.com; location / { root /data/www/frontend; index index.html; try_files $uri $uri/ /index.html; } location /api/ { 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_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } location ~* \\.(jpg|jpeg|png|gif|ico|css|js)$ { root /data/www/frontend; expires 30d; add_header Cache-Control "public, immutable"; }}
为什么能解决 CORS?
因为前端和后端 API 的域名完全一样(都是 www.example.com),浏览器认为是"同源",不会触发跨域检查。
四、 生产场景 B:多套系统统一入口
需求
- 管理后台:
www.example.com/admin - 用户中心:
www.example.com/user - 订单系统:
www.example.com/order
每个系统跑在不同端口,但对外只暴露 80 端口。
配置
server { listen 80; server_name www.example.com; location / { root /data/www/website; index index.html; try_files $uri $uri/ /index.html; } location /admin/ { proxy_pass <http://127.0.0.1:8081/>; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /user/ { proxy_pass <http://127.0.0.1:8082/>; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /order/ { proxy_pass <http://127.0.0.1:8083/>; 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 太多?可以提取到 http 块或独立文件。
创建文件:/usr/local/nginx/conf/proxy_params
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_connect_timeout 60s;proxy_send_timeout 60s;proxy_read_timeout 60s;proxy_buffering on;proxy_buffer_size 4k;proxy_buffers 8 4k;proxy_busy_buffers_size 8k;
然后在 location 中引入:
location /admin/ { proxy_pass <http://127.0.0.1:8081/>; include /usr/local/nginx/conf/proxy_params;}
五、 生产场景 C:WebSocket 反向代理
需求
WebSocket 应用(如在线聊天、实时推送)需要特殊配置,否则会出现连接断开或无法建立连接。
为什么 WebSocket 需要特殊处理?
WebSocket 是长连接协议,需要:
- 升级 HTTP 协议到 WebSocket(HTTP Upgrade)
配置示例
server { listen 80; server_name ws.example.com; location / { proxy_pass <http://127.0.0.1:3000>; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; proxy_buffering off; }}
完整示例:前端 + WebSocket
server { listen 80; server_name chat.example.com; location / { root /data/www/chat-frontend; index index.html; } location /api/ { proxy_pass <http://127.0.0.1:3000/>; include /usr/local/nginx/conf/proxy_params; } location /ws/ { proxy_pass <http://127.0.0.1:3000/>; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; proxy_buffering off; }}
WebSocket 测试
前端 JS 代码:
const ws = new WebSocket('ws://chat.example.com/ws/');ws.onopen = () => { console.log('WebSocket 连接成功'); ws.send('Hello Server!');};ws.onmessage = (event) => { console.log('收到消息:', event.data);};ws.onerror = (error) => { console.error('WebSocket 错误:', error);};ws.onclose = () => { console.log('WebSocket 连接关闭');};
六、 生产场景 D:TCP/UDP 端口转发(四层代理)
需求
有些服务需要直接转发 TCP 端口,比如:
前置条件
确认编译时开启了 stream 模块:
nginx -V 2>&1 | grep -o with-stream# 如果没有输出,需要重新编译./configure --with-stream --with-stream_ssl_modulemake && make install
配置示例:MySQL 代理
注意: stream 块和 http 块是并列关系,不能写在 http 里面。
编辑 nginx.conf:
stream { upstream mysql_backend { server 192.168.1.10:3306; } server { listen 3306; proxy_pass mysql_backend; proxy_connect_timeout 10s; proxy_timeout 300s; }}http { }
完整示例:多服务端口转发
user nginx nginx;worker_processes auto;events { worker_connections 1024;}stream { upstream mysql_backend { server 192.168.1.10:3306 max_fails=3 fail_timeout=30s; } server { listen 3306; proxy_pass mysql_backend; proxy_connect_timeout 10s; proxy_timeout 300s; } upstream redis_backend { server 192.168.1.11:6379; } server { listen 6379; proxy_pass redis_backend; proxy_connect_timeout 10s; proxy_timeout 600s; } upstream postgres_backend { server 192.168.1.12:5432; } server { listen 5432; proxy_pass postgres_backend; proxy_connect_timeout 10s; proxy_timeout 300s; }}http { }
TCP 代理日志配置
stream { log_format proxy '$remote_addr [$time_local] ' '$protocol $status $bytes_sent $bytes_received ' '$session_time "$upstream_addr" ' '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"'; access_log /usr/local/nginx/logs/tcp_access.log proxy; error_log /usr/local/nginx/logs/tcp_error.log warn; }
使用场景
- 数据库隐藏:不直接暴露数据库 IP,统一通过 Nginx 入口
- 端口统一
- 访问控制
- 负载均衡
七、 超时参数详解
不同场景需要不同的超时配置:
HTTP 代理超时
location /api/ { proxy_pass <http://127.0.0.1:8080/>; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s;}
WebSocket 超时
location /ws/ { proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d;}
TCP 代理超时
stream { server { listen 3306; proxy_pass mysql_backend; proxy_connect_timeout 10s; proxy_timeout 300s; }}
推荐配置
八、 缓冲区优化
默认配置(适合大部分场景)
location /api/ { proxy_pass <http://127.0.0.1:8080/>; proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k;}
实时流式传输(禁用缓冲)
适用于:SSE(Server-Sent Events)、视频流、实时日志
location /stream/ { proxy_pass <http://127.0.0.1:8080/>; proxy_buffering off; proxy_cache off;}
九、 常见问题排查
问题 1:502 Bad Gateway
原因:
排查步骤:
ps -ef | grep your_servicesystemctl status your_servicenetstat -tunlp | grep 8080curl <http://127.0.0.1:8080/>tail -f /usr/local/nginx/logs/error.loggetenforcesetenforce 0
问题 2:504 Gateway Timeout
原因: 后端处理时间过长,超过了 Nginx 的超时设置。
解决方案:
location /slow-api/ { proxy_pass <http://127.0.0.1:8080/>; proxy_connect_timeout 120s; proxy_send_timeout 120s; proxy_read_timeout 120s;}
问题 3:WebSocket 连接断开
原因: 超时时间太短或缺少协议升级配置。
解决方案:
location /ws/ { proxy_pass <http://127.0.0.1:3000/>; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s;}
问题 4:后端拿不到真实 IP
原因: 缺少 X-Real-IP 或 X-Forwarded-For 头。
解决方案:
location / { proxy_pass <http://127.0.0.1:8080>; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}
后端获取真实 IP(以 Node.js 为例):
const realIP = req.headers['x-real-ip'] || req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress;
问题 5:TCP 代理连接失败
排查步骤:
nginx -V 2>&1 | grep with-streamnetstat -tunlp | grep 3306mysql -h 192.168.1.10 -P 3306 -u root -ptail -f /usr/local/nginx/logs/tcp_error.logfirewall-cmd --permanent --add-port=3306/tcpfirewall-cmd --reload
10、 安全加固
限制上传大小
server { client_max_body_size 10m; location /upload/ { client_max_body_size 100m; proxy_pass <http://127.0.0.1:8080/>; }}
限制访问来源
location /admin/ { allow 192.168.1.0/24; allow 10.0.0.0/8; deny all; proxy_pass <http://127.0.0.1:8081/>;}
防止 Host 头攻击
server { listen 80 default_server; server_name _; return 444;}server { listen 80; server_name www.example.com; }
隐藏敏感信息
http { server_tokens off; proxy_hide_header X-Powered-By; proxy_hide_header Server;}
11、 完整生产配置模板
文件结构
/usr/local/nginx/conf/├── nginx.conf ├── proxy_params └── conf.d/ ├── www.example.com.conf ├── api.example.com.conf └── ws.example.com.conf
nginx.conf
user nginx nginx;worker_processes auto;error_log logs/error.log warn;pid logs/nginx.pid;events { worker_connections 2048; use epoll;}stream { log_format proxy '$remote_addr [$time_local] $protocol $status'; access_log logs/tcp_access.log proxy; error_log logs/tcp_error.log warn; upstream mysql_backend { server 192.168.1.10:3306 max_fails=3 fail_timeout=30s; } server { listen 3306; proxy_pass mysql_backend; proxy_connect_timeout 10s; proxy_timeout 300s; }}http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; server_tokens off; gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain text/css text/javascript application/json application/javascript application/xml; client_max_body_size 10m; include /usr/local/nginx/conf/conf.d/*.conf;}
proxy_params
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_connect_timeout 60s;proxy_send_timeout 60s;proxy_read_timeout 60s;proxy_buffering on;proxy_buffer_size 4k;proxy_buffers 8 4k;proxy_busy_buffers_size 8k;
conf.d/api.example.com.conf
server { listen 80; server_name api.example.com; access_log logs/api.example.com.access.log main; error_log logs/api.example.com.error.log warn; location /v1/ { proxy_pass <http://127.0.0.1:8080/>; include /usr/local/nginx/conf/proxy_params; } 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_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; proxy_buffering off; } location /health { access_log off; return 200 "ok\\n"; add_header Content-Type text/plain; }}
该文章在 2026/1/22 17:51:47 编辑过