Appearance
Nginx 反向代理
反向代理位于客户端和后端应用之间——客户端访问 Nginx,Nginx 再把请求转发给真实服务:
text
client → nginx → backend在运维场景中,Nginx 作为入口代理通常承担:统一入口(对外只暴露 Nginx 地址)、负载均衡(把请求分发到多个后端)、TLS 终止(证书放在 Nginx 上)、静态资源直返、访问日志记录和基础的限流超时保护。
Nginx 适合做入口代理(TLS 终止、域名路由、静态资源、基础限流、反向代理),但它不是 API 网关——复杂鉴权、租户策略、细粒度路由、服务治理这类能力如果全部写进 Nginx 配置,规则会越来越绕,发布和排查都会变重。小环境可以直接用 Nginx 扛入口,大一些的系统通常还会配专门的网关或应用层治理组件。
一、最小代理配置
后端应用监听 127.0.0.1:8080:
nginx
server {
listen 80;
server_name app.example.local;
location / {
proxy_pass http://127.0.0.1:8080;
}
}验证:
bash
nginx -t && systemctl reload nginx
curl -H 'Host: app.example.local' http://127.0.0.1/二、请求头传递
代理时保留原始 Host 和客户端 IP 很关键——后端应用依赖这些信息生成回调地址、记录审计日志、判断来源 IP:
nginx
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host; # 保留用户访问的域名
proxy_set_header X-Real-IP $remote_addr; # 直接连接 Nginx 的客户端 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # 标记原始请求是 http 还是 https
}各请求头的作用:
| 请求头 | 做什么 |
|---|---|
Host | 用户浏览器里输入的域名,后端据此生成正确的跳转 URL |
X-Real-IP | 直接连到 Nginx 的客户端 IP |
X-Forwarded-For | 完整的代理链路 IP,$proxy_add_x_forwarded_for 是在已有的基础上追加 |
X-Forwarded-Proto | 原始请求协议,后端据此生成 https:// 而非 http:// 的回调地址 |
多层代理时 X-Forwarded-For 会是一串 IP。后端取"真实客户端 IP"时,要配置可信代理 IP 范围——不能直接信任 X-Forwarded-For 最左边那个值,因为客户端可以伪造这个头。
三、proxy_pass 路径规则——斜杠的差别
proxy_pass 末尾有没有 / 会改变转发的 URI:
nginx
# 情况一:proxy_pass 末尾无斜杠
location /api/ {
proxy_pass http://127.0.0.1:8080;
}
# /api/users → 转发到 http://127.0.0.1:8080/api/users(location 前缀保留)
# 情况二:proxy_pass 末尾有斜杠
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
# /api/users → 转发到 http://127.0.0.1:8080/users(location 前缀被替换)线上最常见的代理 404 原因之一就是斜杠差异——本地调试时路径碰巧对得上,上线后后端路由前缀不一样就 404。排查时把 Nginx 配置里的 proxy_pass 和后端实际路由前缀放在一起对着看。
四、upstream 负载均衡
多个后端写成一个 upstream 组:
nginx
upstream app_backend {
server 192.168.10.21:8080;
server 192.168.10.22:8080;
}
server {
listen 80;
server_name app.example.local;
location / {
proxy_pass http://app_backend;
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;
}
}默认是轮询(round-robin),其他分发方式:
| 方式 | 配置 | 适用场景 |
|---|---|---|
| 轮询 | 默认,不写就是 | 后端规格一致 |
| 权重 | server ... weight=2 | 后端机器配置不同 |
| 最少连接 | least_conn; | 请求耗时差异大(长短连接混合) |
| IP hash | ip_hash; | 简单的会话保持 |
权重示例:
nginx
upstream app_backend {
server 192.168.10.21:8080 weight=2; # 这台配得高,分到更多请求
server 192.168.10.22:8080 weight=1;
}五、后端故障和重试
开源版 Nginx 的 upstream 健康检查是被动的——请求失败后才把后端临时标记为不可用:
nginx
upstream app_backend {
server 192.168.10.21:8080 max_fails=3 fail_timeout=30s;
server 192.168.10.22:8080 max_fails=3 fail_timeout=30s;
}| 参数 | 含义 |
|---|---|
max_fails=3 | 在 fail_timeout 时间内失败 3 次后,该后端被临时摘除 |
fail_timeout=30s | 失败计数的统计窗口,也是摘除持续的时间 |
代理层重试:
nginx
location / {
proxy_pass http://app_backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2; # 最多尝试 2 个后端,避免无限重试放大流量
}重试只对幂等请求(GET)安全。下单、支付、创建任务这类请求如果在入口层被重试,后端可能收到两次写入——所以应用自身要有幂等保护,不能依赖 Nginx 层面不重试。
六、超时和缓冲
nginx
location / {
proxy_pass http://app_backend;
proxy_connect_timeout 3s; # 连接后端的超时——连不上时快速失败
proxy_send_timeout 30s; # 向后端发送请求体的超时
proxy_read_timeout 30s; # 等待后端返回响应的超时——接口慢时先看这个
}三个超时的分工:
| 参数 | 超时发生在哪个阶段 |
|---|---|
proxy_connect_timeout | TCP 握手阶段——后端端口是否可达 |
proxy_send_timeout | 发送请求体阶段——Nginx 把请求数据发给后端 |
proxy_read_timeout | 等待响应阶段——后端多久没返回数据,最常见超时场景 |
缓冲配置:
nginx
location / {
proxy_pass http://app_backend;
proxy_buffering on; # 开启缓冲:Nginx 收完后端响应再发给客户端
proxy_buffers 16 16k;
proxy_busy_buffers_size 64k;
}缓冲对普通 HTTP 接口是好事——后端快慢都先由 Nginx 扛着,客户端不会直接感受到后端的抖动。但流式响应(SSE、文件下载、实时推送)需要关闭缓冲,否则客户端迟迟看不到数据:
nginx
location /events/ {
proxy_pass http://app_backend;
proxy_buffering off; # 流式响应:后端吐一点 Nginx 就传一点
proxy_read_timeout 1h; # 长连接放宽超时
}七、WebSocket
WebSocket 需要协议升级:
nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close; # 没有 Upgrade 头时,Connection 设为 close
}
server {
listen 80;
server_name ws.example.local;
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1; # WebSocket 必须 HTTP/1.1
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1h; # 长连接超时放宽
}
}WebSocket 断连时,除了看 Nginx 配置,还要排查链路上每一层:中间负载均衡的空闲超时、浏览器控制台的错误信息、后端应用的连接日志。很多断连不是单点问题,而是某一层空闲超时设得太短。
八、日志——区分 Nginx 耗时和后端耗时
默认日志不够排查代理问题,加一个自定义格式把 upstream 状态和耗时打出来:
nginx
log_format proxy_main '$remote_addr - $host "$request" '
'status=$status body=$body_bytes_sent '
'request_time=$request_time '
'upstream=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_time=$upstream_response_time';
access_log /var/log/nginx/app.access.log proxy_main;关键字段:
| 字段 | 含义 |
|---|---|
$request_time | Nginx 从收到请求到完成响应的总耗时(秒) |
$upstream_addr | 实际转发到的后端地址 |
$upstream_status | 后端返回的 HTTP 状态码 |
$upstream_response_time | 后端处理耗时(秒) |
接口慢时,这几个字段能区分是谁的问题:$request_time 高、$upstream_response_time 也高 → 后端慢;$request_time 高但 $upstream_response_time 正常 → 客户端传输慢或 Nginx 侧排队;$upstream_status 是 502/504 → 后端挂了或超时。
九、常见故障
| 现象 | 常见原因 | 排查方向 |
|---|---|---|
| 502 Bad Gateway | 后端端口不可达、进程挂了、协议不对 | ss -lntp 看端口、后端日志、Nginx error log |
| 504 Gateway Timeout | 后端响应超过 proxy_read_timeout | 后端耗时、数据库查询、外部接口调用 |
| 404(代理后) | proxy_pass 斜杠导致路由错位 | 对比 Nginx 转发路径和后端路由 |
| 后端拿不到真实 IP | 请求头没传或后端没信任代理 | X-Forwarded-For、应用框架代理配置 |
| 只有一个后端有流量 | upstream 配置写了多台但只有一台被用到 | access 日志里的 $upstream_addr |
| 大文件上传失败 | client_max_body_size 默认 1MB | 加大限制:client_max_body_size 100m; |
排查入口:先看 access 日志里的状态码、$upstream_addr 和耗时,再看 error 日志。只看浏览器里一个 502,无法判断是 Nginx、网络还是后端的问题。