一台 4 核 8G 的云服务器上,你同时跑了公司官网(Tomcat 8080)、订单接口(Go 服务 9000)、运营后台(Node.js 3000)和一个老旧的 PHP 站点(PHP-FPM)。防火墙只愿意放行 80 和 443,客户也不接受访问 example.com:8080 这种带端口的网址。这时候最省事、也最可靠的做法,就是让 Nginx 站在最前面当统一入口:对外只有一个域名和一个端口,对内按域名或路径把请求转发给不同的后端服务。这就是反向代理(Reverse Proxy)最典型的落地场景。
本文从“反向代理到底替你做了什么”讲起,逐层展开 proxy_pass、请求头透传、upstream 负载均衡、多站点分发的两种写法,以及上线前必须做的检查。所有配置均以 Nginx 1.18+(Ubuntu 20.04 / CentOS 7 默认源版本)为准,路径采用 /etc/nginx/nginx.conf 与 /etc/nginx/conf.d/ 的通用约定。
一、反向代理到底解决什么问题
很多新手把反向代理简单理解成“转发端口”,其实它承担的职责远不止转发。理解清楚这五件事,后面配置时才不会乱:
- 端口收敛:对外只暴露 80/443,后端服务可以只监听
127.0.0.1,从根本上减少攻击面。 - 统一入口与域名路由:一台机器上托管多个域名,靠
server_name区分,互不干扰。 - TLS 终结:证书只在 Nginx 上配置一次,后端全部走 HTTP,省去每个服务自己配证书的麻烦。
- 负载分发:通过
upstream把流量摊到多个后端实例,并自动剔除故障节点。 - 缓冲与抗压:Nginx 先把后端响应读完再慢慢吐给慢速客户端,避免慢连接把后端进程拖死。
1.1 正向代理与反向代理的区别
两个概念经常被混着说,判断标准很简单:代理的是谁。正向代理代理客户端,服务端不知道真实用户是谁;反向代理代理服务端,客户端不知道真实服务是谁。
| 对比项 | 正向代理 | 反向代理 |
|---|---|---|
| 代理对象 | 客户端(浏览器、内网机器) | 服务端(网站、接口) |
| 典型用途 | 内网上网、访问加速、隐藏访问者 | 发布网站、负载均衡、隐藏后端 |
| 客户端是否知情 | 需要手动配置代理地址 | 无需配置,以为直连 |
| 典型软件 | Squid、Shadowsocks | Nginx、HAProxy、Envoy |
| 配置关键指令 | resolver + proxy_pass $scheme://$host$request_uri | proxy_pass http://backend; + proxy_set_header |
1.2 一个典型的多站点拓扑
公网客户端
│ https://www.a.com https://api.a.com
▼
┌─────────────────────────────────────────┐
│ Nginx (80/443,TLS 终结,反向代理) │
│ www.a.com → 127.0.0.1:8080 (Tomcat)│
│ api.a.com/api → 127.0.0.1:9000 (Go) │
│ admin.a.com → 127.0.0.1:3000 (Node) │
└─────────────────────────────────────────┘
注意拓扑里后端全部绑定 127.0.0.1。这是多站点部署的第一条安全纪律:数据库、缓存、内部接口一律不监听 0.0.0.0。
二、最小可用配置:proxy_pass 与请求头透传
先写一个能跑起来的最小配置,放在 /etc/nginx/conf.d/www.a.com.conf:
server {
listen 80;
server_name www.a.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_http_version 1.1;
proxy_set_header Connection "";
# 超时控制(秒)
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
逐条解释几个容易漏掉的点:
Host $host:默认 Nginx 会把 Host 改写成proxy_pass里的地址。后端若按 Host 做虚拟主机路由,不写这一条会全部落到默认站点。X-Real-IP/X-Forwarded-For:后端日志里想看到真实访客 IP 就必须透传。$proxy_add_x_forwarded_for会在原有值后追加,比直接写$remote_addr更安全。X-Forwarded-Proto $scheme:后端据此判断当前是 http 还是 https,否则生成的跳转链接可能退化成 http,造成循环重定向。proxy_http_version 1.1+Connection "":配合upstream里的keepalive使用,能显著减少 TIME_WAIT 和握手开销。
2.1 proxy_pass 结尾带不带“/”,行为完全不同
这是 Nginx 反向代理配置中最高频的坑,务必记牢:带 URI(以 / 结尾)时会替换掉 location 匹配到的前缀,不带时原样透传完整 URI。
| location | proxy_pass 写法 | 请求 /api/user?id=1 转发结果 |
|---|---|---|
| location /api/ | proxy_pass http://127.0.0.1:9000; | /api/user?id=1(原样透传) |
| location /api/ | proxy_pass http://127.0.0.1:9000/; | /user?id=1(前缀被替换) |
| location /api | proxy_pass http://127.0.0.1:9000/v1/; | /v1/user?id=1 |
| location ~ ^/api/(.*)$ | proxy_pass http://127.0.0.1:9000/$1; | 由正则捕获组决定 |
实践建议:如果后端服务的路径和前端访问路径一致,就不要在 proxy_pass 后面加 /;只有在需要“剥掉前缀”时才加。
三、upstream 与负载均衡:从单实例到多实例
当后端跑多个实例时,把地址写死在每个 location 里会失控。upstream 块把后端池单独定义,还能配置健康检查和调度策略。
upstream backend_api {
# 默认轮询;least_conn 适合长连接/耗时不均的接口
least_conn;
server 127.0.0.1:9001 weight=3 max_fails=3 fail_timeout=30s;
server 127.0.0.1:9002 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:9003 backup;
keepalive 32;
}
server {
listen 80;
server_name api.a.com;
location / {
proxy_pass http://backend_api;
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_http_version 1.1;
proxy_set_header Connection "";
}
}
3.1 调度策略与状态参数速查
| 指令/参数 | 含义 | 适用场景 |
|---|---|---|
| (默认)轮询 round-robin | 依次分发,配合 weight 加权 | 后端性能接近、请求耗时均匀 |
| least_conn | 优先给当前连接数最少的节点 | 长连接、请求耗时差异大 |
| ip_hash | 按客户端 IP 前三段哈希固定节点 | 无 Redis 的会话保持(不推荐长期依赖) |
| hash $request_uri consistent | 按 URI 一致性哈希 | 后端自带本地缓存,提高命中率 |
| weight=N | 权重,默认 1 | 机器配置不一致时按比例分配 |
| max_fails / fail_timeout | 30s 内失败 3 次则摘除 30s(默认值即 1 次 / 10s) | 故障自动摘除 |
| backup | 仅当主节点全挂时接管 | 灾备实例 |
| down | 手动标记为不可用 | 灰度摘除、发版维护 |
| keepalive 32 | 每个 worker 保持的空闲长连接数 | 高并发下减少握手 |
提醒:max_fails 与 fail_timeout 是“被动健康检查”,只有真实请求失败才会触发。开源版 Nginx 没有主动心跳探测,如需主动探测可用 nginx_upstream_check_module 第三方模块,或直接上 Nginx Plus。
四、多站点转发的两种写法
4.1 按域名分发(推荐)
不同业务用不同域名,各写一个 server 块,彼此完全隔离,日志、证书、限流都能单独配。这是多站点部署的首选方式。
server {
listen 80;
server_name www.a.com a.com;
access_log /var/log/nginx/www.a.com.access.log main;
location / { proxy_pass http://127.0.0.1:8080; include /etc/nginx/snippets/proxy_headers.conf; }
}
server {
listen 80;
server_name admin.a.com;
access_log /var/log/nginx/admin.a.com.access.log main;
location / { proxy_pass http://127.0.0.1:3000; include /etc/nginx/snippets/proxy_headers.conf; }
# 后台只允许办公网访问
allow 203.0.113.0/24;
deny all;
}
把重复的 proxy_set_header 抽到 /etc/nginx/conf.d/proxy_headers.conf,用 include 引入,配置会清爽很多,也避免漏配。
4.2 按路径分发
只有一个域名、需要把 /api/ 打到独立后端时用路径分发。注意 WebSocket 需要额外升级头:
server {
listen 80;
server_name www.a.com;
location / {
root /data/www/a;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:9000;
include /etc/nginx/snippets/proxy_headers.conf;
}
location /ws/ {
proxy_pass http://127.0.0.1:9001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
}
4.3 location 匹配优先级
多站点配置出 Bug,八成是 location 没按你以为的方式匹配。优先级从高到低:
=精确匹配,命中即返回,如location = /health^~前缀匹配且不再往下看正则,如location ^~ /static/~/~*正则匹配(区分/不区分大小写),按配置出现顺序取第一个- 普通前缀匹配,取最长的那条
/兜底
判断口诀:先看有没有精确和 ^~,再看正则,最后比前缀长度。
五、上线前的检查与验证流程
改完配置直接 reload 出事是运维常态。按下面清单走一遍,能挡掉绝大多数低级错误。
- 语法与完整配置检查:
nginx -t校验语法,nginx -T打印合并后的全部配置(排查 include 覆盖问题时尤其有用)。 - 平滑加载:
systemctl reload nginx或nginx -s reload,不要用restart,避免断连。 - 确认后端真的在听:
ss -lntp | grep -E '8080|9000|3000',重点看监听地址是不是127.0.0.1还是被误绑到公网。 - 本机直连后端验证:
curl -H 'Host: www.a.com' -I http://127.0.0.1:8080/,先确认后端本身健康。 - 走代理验证:
curl -I http://www.a.com/,对比状态码与响应头。 - 确认 IP 透传生效:在后端打印
X-Forwarded-For,或用curl -H 'X-Real-IP: 1.2.3.4'配合日志观察。 - 看错误日志:
tail -f /var/log/nginx/error.log,出现connect() failed (111: Connection refused)说明后端没起;(110: Connection timed out)说明被防火墙或超时拦了。
5.1 顺手加上的安全与性能项
# 隐藏版本号 server_tokens off; # 限制客户端请求体大小,防止大文件上传打爆后端 client_max_body_size 20m; # 关闭代理重定向改写(后端返回 301 时交给客户端自己跳) proxy_redirect off; # 失败重试策略:超时/502 时换节点,但 POST 不重试避免重复下单 proxy_next_upstream error timeout http_502; proxy_next_upstream_tries 2;
小结
Nginx 反向代理的核心就三件事:用 proxy_pass 把请求送到正确的地方,用 proxy_set_header 把客户端信息完整交给后端,用 upstream 把多个实例组织成一个池子。多站点部署优先按域名拆分 server 块,其次才考虑路径分发。
最后列一份避坑清单,建议贴在工位上:
proxy_pass结尾的/决定路径是否被替换,改之前先想清楚。- 漏配
Host头会导致后端虚拟主机路由错乱;漏配X-Forwarded-Proto会导致 HTTPS 站点无限重定向。 - 换了
proxy_pass地址后必须nginx -t再reload,不要直接restart。 - 后端服务一律监听
127.0.0.1,端口不开防火墙,代理即边界。 proxy_read_timeout默认 60s,明显短于后端业务耗时的接口(导出、报表)要单独调大,否则必然 504。- 用
ip_hash做会话保持是权宜之计,长期方案是将会话外置到 Redis。 - 日志按
server_name分文件,否则多站点共用一个 access.log,排查时根本无法定位。
关键词回顾:Nginx 反向代理配置、多站点转发、proxy_pass、upstream 负载均衡、proxy_set_header 透传、后端服务部署。
企业QQ咨询




