nginx —— 反向代理、负载均衡与排障
nginx —— 反向代理、负载均衡与排障
分类:12 隧道与高可用 适用:Nginx 1.18 ~ 1.27(开源版;部分指令为 Plus 专有) 一句话:七层入口的标准件,做反向代理、负载均衡、TLS 卸载、限流与灰度。
1. 它解决什么问题
- 一个入口后面挂多个后端,做负载与故障转移。
- 统一处理 TLS、gzip、跨域、限流。
- 502/504 频发,要定位是网关还是后端。
- 按路径/域名/Header 分流(灰度、多租户)。
2. 配置结构与校验
/etc/nginx/
├── nginx.conf # 主配置
├── conf.d/*.conf # 站点配置
├── sites-enabled/* # Debian 系
├── mime.types
└── snippets/
nginx -t # 语法检查(改完必做)
nginx -T # 打印合并后的完整配置
nginx -s reload # 平滑重载
nginx -s quit # 优雅退出
nginx -s stop # 快速停止
nginx -v / -V # 版本 / 编译参数
任何配置变更:nginx -t 通过后再 reload。reload 不会断开现有连接。
3. 最小反向代理
upstream app_backend {
server 10.0.0.11:8080 weight=5 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 weight=5 max_fails=3 fail_timeout=10s;
keepalive 64; # 与后端保持长连接
keepalive_requests 1000;
keepalive_timeout 60s;
}
server {
listen 80;
server_name app.example.com;
access_log /var/log/nginx/app.access.log main;
error_log /var/log/nginx/app.error.log warn;
client_max_body_size 20m;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503;
}
}
proxy_pass 的斜杠陷阱
location /api/ {
proxy_pass http://backend; # 保留 /api/ → 后端收到 /api/xxx
}
location /api/ {
proxy_pass http://backend/; # 去掉 /api/ → 后端收到 /xxx
}
尾部有无 / 决定是否截断 location 前缀,这是最高频的配置事故。
4. location 匹配优先级
= 精确 → 最高
^~ 前缀 → 次高(匹配后不再查正则)
~ 正则 → 按出现顺序
~* 正则忽略大小写
无修饰前缀 → 最低(最长匹配)
location = /health { return 200 "ok\n"; }
location ^~ /static/ { root /data; }
location ~* \.(jpg|png|css|js)$ { expires 30d; }
location / { proxy_pass http://app_backend; }
= /health 放在最前,避免被 location / 拦截。
5. 日志格式(排障基础)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'rt=$request_time urt=$upstream_response_time '
'ua=$upstream_addr us=$upstream_status '
'rid=$request_id';
access_log /var/log/nginx/access.log main;
| 变量 | 含义 |
|---|---|
$request_time |
Nginx 从收到请求到发送完响应的总时间 |
$upstream_response_time |
后端处理时间 |
$upstream_addr |
实际转发的后端 |
$upstream_status |
后端返回状态码 |
$request_id |
请求唯一 ID(贯穿日志) |
判读:
rt大、urt小 → 慢在客户端或网络回传。rt ≈ urt且都大 → 后端慢。urt为-→ 请求没到后端(Nginx 自己返回,如 499、413)。
6. 限流与限连接
# 定义在 http 块
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_perip:10m;
server {
location /api/ {
limit_req zone=perip burst=20 nodelay;
limit_conn conn_perip 20;
limit_req_status 429;
limit_conn_status 429;
proxy_pass http://app_backend;
}
}
| 参数 | 含义 |
|---|---|
rate=10r/s |
每秒 10 个请求 |
burst=20 |
允许突发 20 个排队 |
nodelay |
突发请求立即处理(否则排队变慢) |
zone 的 10m 约存 16 万个 IP 状态。
7. TLS 配置
server {
listen 443 ssl;
http2 on; # 1.25.1+ 写法;老版本用 listen 443 ssl http2
server_name app.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem; # 必须含中间证书
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
add_header Strict-Transport-Security "max-age=31536000" always;
}
HTTP 跳 HTTPS:
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
8. 502 / 504 排查流程
502 Bad Gateway
含义:Nginx 连不上后端,或后端返回了非法响应。
# 1. 看错误日志(关键)
tail -100 /var/log/nginx/error.log
# 典型:connect() failed (111: Connection refused) while connecting to upstream
# upstream prematurely closed connection
# 2. 从 Nginx 机器直连后端
curl -v http://10.0.0.11:8080/health
nc -zv 10.0.0.11 8080
# 3. 后端是否在跑、是否只监听 127.0.0.1
ss -lntp | grep 8080
# 4. 看后端日志(崩溃/超时/重启)
journalctl -u app -n 100
# 5. SELinux / 防火墙
getenforce; firewall-cmd --list-all
常见原因:后端挂了、端口错、只绑 127.0.0.1、client_max_body_size 太小(413 不是 502)、后端主动断连、PHP-FPM 进程不够。
504 Gateway Timeout
含义:后端在超时时间内没返回。
grep 'upstream timed out' /var/log/nginx/error.log | tail
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 300s; # 长任务按需放大
但先确认后端为什么慢:$upstream_response_time、后端 APM、数据库慢查询。盲目调大超时只会把问题藏起来。
499 Client Closed Request
客户端主动断开(用户关页面、上游超时)。大量 499 通常是响应太慢导致客户端放弃。
9. 实战场景
9.1 灰度发布(按 Header / Cookie)
upstream app_v1 { server 10.0.0.11:8080; }
upstream app_v2 { server 10.0.0.12:8080; }
map $http_x_canary $backend_pool {
default app_v1;
"true" app_v2;
}
server {
location / {
proxy_pass http://$backend_pool; # 注意:变量方式不做健康检查
proxy_set_header Host $host;
}
}
proxy_pass用变量时 Nginx 会在运行时解析,失去 upstream 的健康检查与连接池,生产更推荐用split_clients或按权重调整。
9.2 按权重分流
upstream app_backend {
server 10.0.0.11:8080 weight=9; # 90%
server 10.0.0.12:8080 weight=1; # 10%
}
9.3 静态资源与后端分离
location /static/ {
alias /data/static/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location / {
proxy_pass http://app_backend;
}
root 会拼接完整 URI,alias 会替换 location 部分,注意区分。
9.4 开启 stub_status 监控
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
Waiting 高说明 keepalive 空闲连接多;Reading+Writing 高说明并发处理中。
9.5 大文件上传 413
client_max_body_size 100m;
proxy_request_buffering off; # 大文件直传后端,避免磁盘缓冲
9.6 查看生效配置
nginx -T | grep -A10 'server_name app.example.com'
排查"改了不生效":确认改的是被 include 的文件、nginx -t 是否通过、是否 reload、是否有更高优先级的 server 块。
10. 常见坑
proxy_pass尾部斜杠:路径被截断或重复,见第 3 节。Host头没传:后端生成错误的绝对 URL、防盗链失效。- 没配
X-Forwarded-For:后端拿到的都是 Nginx 的 IP,日志与限流失效。 keepalive配了但没生效:必须同时proxy_http_version 1.1+proxy_set_header Connection ""。client_max_body_size默认 1m:上传大文件报 413。location优先级误判:正则 location 会抢先于普通前缀,用^~明确。root与alias混用:路径拼错,404 且日志看不出原因。- reload 后旧 worker 不退出:长连接未断,
worker_shutdown_timeout控制。 worker_connections不足:报worker_connections are not enough,同时要调worker_rlimit_nofile。- upstream 健康检查是"被动"的:开源版没有主动健康检查,靠
max_fails/fail_timeout,恢复慢。 proxy_buffering与 SSE/流式:需要proxy_buffering off才能实时输出。- 证书链不全:浏览器正常但 Java 客户端报错,
ssl_certificate要用 fullchain。 - 日志没切:
access.log涨满磁盘,配logrotate并nginx -s reopen。 - 配置改错导致 reload 失败:旧配置仍在运行,容易误以为已生效,务必看
nginx -t输出。
11. 速查表
nginx -t / -T
nginx -s reload / quit / stop / reopen
ps -ef | grep nginx
ss -lntp | grep nginx
# 排障
tail -f /var/log/nginx/error.log
awk '{print $9}' access.log | sort | uniq -c | sort -nr
grep 'upstream timed out' error.log | tail
grep -c ' 502 ' access.log
# 关键指令
proxy_pass http://backend; # 保留前缀
proxy_pass http://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_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 60s;
client_max_body_size 20m;
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_req zone=perip burst=20 nodelay;
upstream x { server ip:port weight=5 max_fails=3 fail_timeout=10s; keepalive 64; }