Nginx 反向代理实战
大约 12 分钟约 3468 字
Nginx 反向代理实战
简介
Nginx 反向代理最常见的用途,是把外部流量统一接入到后端应用、静态资源服务、WebSocket 服务或多个上游节点。它不仅承担"转发请求"的角色,还经常负责协议转换、请求头透传、连接复用、超时控制、限流、缓存和灰度流量分发。
在生产环境中,Nginx 反向代理是流量治理的基础设施层。它位于客户端和后端服务之间,承担着请求路由、负载均衡、安全防护、性能优化等多重职责。一个设计良好的反向代理配置,可以有效降低后端服务的复杂度,同时提升整体系统的可用性和性能。
特点
反向代理核心原理
请求处理流程
# Nginx 反向代理请求处理流程
[客户端请求]
|
v
[Nginx 接收请求]
|
+-- 解析请求头、URI、参数
+-- 匹配 server_name 和 location
+-- 执行 rewrite 规则
+-- 应用 access/deny 规则
|
v
[代理处理]
|
+-- DNS 解析上游地址(如果使用域名)
+-- 建立到上游的连接
+-- 转发请求头(含 proxy_set_header)
+-- 发送请求体
|
v
[等待上游响应]
|
+-- 读取上游响应头
+-- 读取上游响应体(缓冲或流式)
+-- 处理上游错误(超时、5xx)
|
v
[返回客户端]
|
+-- 写入响应头
+-- 写入响应体
+-- 关闭连接(或保持 keepalive)proxy_pass 路径拼接规则
# proxy_pass 路径拼接规则(核心知识点)
# 规则一:proxy_pass 带 URI(路径会替换)
location /api/ {
proxy_pass http://backend/new-api/;
}
# 请求 /api/users -> 上游 /new-api/users
# 规则二:proxy_pass 不带 URI(路径原样传递)
location /api/ {
proxy_pass http://backend;
}
# 请求 /api/users -> 上游 /api/users
# 规则三:proxy_pass 带端口不带路径
location /api/ {
proxy_pass http://backend:8080;
}
# 请求 /api/users -> 上游 /api/users
# 规则四:使用 rewrite + break
location /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://backend;
}
# 请求 /api/users -> 上游 /users
# 规则五:使用正则 location + proxy_pass(不能带 URI)
location ~ ^/api/(.*)$ {
proxy_pass http://backend/$1;
}
# 请求 /api/users -> 上游 /users实现
基础反向代理与 upstream 配置
# /etc/nginx/conf.d/app.conf
# upstream 块定义后端服务器组
upstream app_backend {
# 后端服务器地址
server 10.0.0.21:5000 max_fails=3 fail_timeout=10s;
server 10.0.0.22:5000 max_fails=3 fail_timeout=10s;
# 备用节点(主节点全部不可用时启用)
server 10.0.0.23:5000 backup;
# 保持与上游的长连接
keepalive 64;
}
server {
listen 80;
server_name api.example.com;
# 访问日志(包含代理相关字段)
access_log /var/log/nginx/api_access.log proxy_format;
error_log /var/log/nginx/api_error.log warn;
location / {
proxy_pass http://app_backend;
# HTTP 协议版本(1.1 支持 keepalive)
proxy_http_version 1.1;
# 请求头透传(关键!)
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# 关闭 Connection: close(配合 keepalive)
proxy_set_header Connection "";
# 超时配置
proxy_connect_timeout 3s; # 连接上游超时
proxy_send_timeout 30s; # 发送请求到上游超时
proxy_read_timeout 30s; # 读取上游响应超时
# 失败切换
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
}# 自定义日志格式(用于排障和追踪)
log_format proxy_format '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_response_time=$upstream_response_time '
'request_time=$request_time '
'http_x_forwarded_for=$http_x_forwarded_for';# 配置校验与重载
nginx -t
systemctl reload nginx
# 验证代理是否正常
curl -I http://api.example.com/
curl -v http://api.example.com/health
# 查看代理日志
tail -f /var/log/nginx/api_access.log
tail -f /var/log/nginx/api_error.log
# 查看上游状态(需要 stub_status 模块)
curl http://127.0.0.1:8080/nginx_status# 按路径拆分不同后端
server {
listen 80;
server_name gateway.example.com;
# API 路由到后端
location /api/ {
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;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
}
# 管理后台路由到独立后端
location /admin/ {
proxy_pass http://10.0.0.30:7001/;
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_read_timeout 60s;
}
# 前端静态文件
location / {
root /opt/frontend/dist;
try_files $uri $uri/ /index.html;
# 静态文件缓存
expires 30d;
add_header Cache-Control "public, immutable";
}
# 健康检查端点(不记录日志)
location /health {
proxy_pass http://app_backend/health;
access_log off;
}
}WebSocket、上传与大响应优化
# WebSocket 代理
# 必须处理 Upgrade 和 Connection 头
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location /socket/ {
proxy_pass http://10.0.0.40:6001;
proxy_http_version 1.1;
# WebSocket 关键头
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;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 长连接超时(必须足够长)
proxy_read_timeout 600s;
proxy_send_timeout 600s;
# 禁用缓冲(WebSocket 实时性要求)
proxy_buffering off;
}
}# 文件上传代理
server {
listen 80;
server_name upload.example.com;
# 上传文件大小限制
client_max_body_size 200m;
# 上传超时
client_body_timeout 60s;
client_header_timeout 30s;
location /upload/ {
proxy_pass http://10.0.0.50:8080;
# 关闭请求缓冲(流式上传,减少内存占用)
proxy_request_buffering off;
# 上传超时(大文件需要更长超时)
proxy_connect_timeout 10s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 透传头
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 Content-Type $content_type;
proxy_set_header Content-Length $content_length;
}
}# 大响应和下载优化
location /download/ {
proxy_pass http://10.0.0.60:9000;
# 开启缓冲(Nginx 先完整接收上游响应,再发给客户端)
proxy_buffering on;
# 缓冲区配置
proxy_buffers 16 64k; # 16 个 64KB 缓冲区
proxy_buffer_size 64k; # 响应头缓冲区大小
proxy_busy_buffers_size 128k; # 忙时缓冲区大小
proxy_temp_file_write_size 64k; # 临时文件写入大小
# 临时文件大小限制(0 = 不限制,大文件写入磁盘)
proxy_max_temp_file_size 1024m;
# 发送优化
sendfile on;
tcp_nopush on;
tcp_nodelay off;
# 超时(大文件下载需要较长超时)
proxy_connect_timeout 10s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}# API 响应缓冲优化(JSON/API 响应)
location /api/ {
proxy_pass http://app_backend;
# 小响应直接发送,大响应先缓冲
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
# 响应压缩
gzip on;
gzip_types application/json application/xml text/plain;
gzip_min_length 1024;
}负载均衡策略
# 轮询(Round Robin,默认策略)
upstream order_api {
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}
# 加权轮询(Weighted Round Robin)
upstream search_api {
server 10.0.1.21:8080 weight=3; # 3/4 的流量
server 10.0.1.22:8080 weight=1; # 1/4 的流量
}
# 最少连接(Least Connections)
upstream api_servers {
least_conn;
server 10.0.1.31:8080;
server 10.0.1.32:8080;
server 10.0.1.33:8080;
}
# IP 哈希(会话保持)
upstream session_api {
ip_hash;
server 10.0.1.41:8080;
server 10.0.1.42:8080;
}
# 通用哈希(按任意 key 做一致性哈希)
upstream cache_api {
hash $request_uri consistent;
server 10.0.1.51:8080;
server 10.0.1.52:8080;
server 10.0.1.53:8080;
}# upstream 健康检查与故障处理
upstream api_backend {
# max_fails: 最大失败次数
# fail_timeout: 失败后暂停时间(同时作为失败计数的时间窗口)
server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=10s;
# 备用节点
server 10.0.1.13:8080 backup;
# 标记为下线(用于灰度发布)
# server 10.0.1.14:8080 down;
}
# 主动健康检查(需要 nginx_upstream_check_module 或商业版)
upstream api_backend {
server 10.0.1.11:8080;
server 10.0.1.12:8080;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "HEAD /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}灰度发布与流量分发
# 灰度发布:按 Cookie 分流
map $cookie_canary $app_pool {
default stable_pool;
"v2" canary_pool;
}
upstream stable_pool {
server 10.0.2.11:5000;
server 10.0.2.12:5000;
}
upstream canary_pool {
server 10.0.2.21:5000;
}
server {
listen 80;
server_name canary.example.com;
location / {
proxy_pass http://$app_pool;
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;
}
}# 灰度发布:按请求头分流
split_clients "${remote_addr}" $variant {
10% canary;
90% stable;
}
upstream stable_backend {
server 10.0.2.11:5000;
server 10.0.2.12:5000;
}
upstream canary_backend {
server 10.0.2.21:5000;
}
server {
listen 80;
server_name canary.example.com;
location / {
proxy_pass http://${variant}_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;
}
}# 灰度发布:按 IP 段分流
geo $canary_region {
default 0;
10.0.3.0/24 1; # 内测 IP 段
192.168.1.0/24 1; # 办公室 IP 段
}
upstream stable_backend {
server 10.0.2.11:5000;
}
upstream canary_backend {
server 10.0.2.21:5000;
}
server {
listen 80;
server_name canary.example.com;
location / {
if ($canary_region) {
proxy_pass http://canary_backend;
break;
}
proxy_pass http://stable_backend;
}
}故障切换与错误处理
# 故障切换配置
location / {
proxy_pass http://app_backend;
# 触发切换的上游错误类型
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3; # 最多尝试 3 个上游
proxy_next_upstream_timeout 10s; # 切换总超时
proxy_next_upstream_http_version 1.1;
# 错误页处理
error_page 502 503 504 /50x.html;
}
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
# 自定义错误页
error_page 502 /502.html;
location = /502.html {
root /usr/share/nginx/html;
internal;
}# 根据上游响应做处理
location / {
proxy_pass http://app_backend;
# 上游响应特定状态码时重定向
proxy_intercept_errors on;
error_page 404 = @not_found;
error_page 500 502 503 504 = @server_error;
}
location @not_found {
default_type application/json;
return 404 '{"error": "not found", "code": 404}';
}
location @server_error {
default_type application/json;
return 503 '{"error": "service unavailable", "code": 503}';
}安全防护
# IP 黑白名单
location /admin/ {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
proxy_pass http://admin_backend;
}
# 请求频率限制
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
server {
listen 80;
server_name api.example.com;
location /api/ {
limit_req zone=api_limit burst=50 nodelay;
limit_req_status 429;
proxy_pass http://app_backend;
}
}
# 请求体大小限制
server {
client_max_body_size 10m;
client_body_buffer_size 128k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
}
# 隐藏版本信息
server_tokens off;
# 禁止特定 User-Agent
if ($http_user_agent ~* (bot|crawler|spider)) {
return 403;
}
# 安全响应头
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'" always;缓存配置
# 代理缓存配置
proxy_cache_path /var/cache/nginx/api
levels=1:2
keys_zone=api_cache:100m
max_size=10g
inactive=60m
use_temp_path=off;
server {
listen 80;
server_name api.example.com;
# GET 请求缓存
location /api/public/ {
proxy_pass http://app_backend;
proxy_cache api_cache;
proxy_cache_key $scheme$proxy_host$request_uri;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
add_header X-Cache-Status $upstream_cache_status;
}
# 不缓存动态接口
location /api/private/ {
proxy_pass http://app_backend;
proxy_no_cache 1;
proxy_cache_bypass 1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}# 缓存管理
# 清除缓存
rm -rf /var/cache/nginx/api/*
# 查看缓存命中率
# 在 access_log 中记录 $upstream_cache_status
# HIT: 命中缓存
# MISS: 未命中
# EXPIRED: 缓存过期
# STALE: 使用了过期缓存
# UPDATING: 正在更新缓存优点
缺点
总结
Nginx 反向代理不是单纯的"转发器",而是流量治理层的一部分。落地时至少要把 upstream、请求头透传、连接/读取超时、WebSocket 升级和错误切换设计清楚;在复杂场景下,再逐步增加缓存、灰度、限流和日志追踪能力。
关键知识点
proxy_pass后是否带/,会直接影响路径拼接结果- 真实 IP、Host、协议头不透传,后端经常会拿到错误上下文
- WebSocket 必须处理
Upgrade和Connection头 proxy_next_upstream影响失败时是否切到其他后端节点proxy_buffering默认开启,WebSocket 场景需要关闭proxy_set_header Connection ""是配合 keepalive 的必要设置- 日志中的
$upstream_addr和$upstream_status是排障的关键字段
项目落地视角
- 前后端分离项目由 Nginx 同时代理静态页面与后端 API
- WebSocket 即时通知服务通过独立 location 保持长连接
- 管理后台灰度发布通过 Cookie / Header 定向分流验证新版本
- 文件上传、报表下载等大流量接口单独配置超时和缓冲策略
- 对外 API 和内部管理接口分别配置限流和访问控制
- 静态资源配置浏览器缓存和 CDN 加速
常见误区
- 只写
proxy_pass,忘记透传Host和X-Forwarded-* - WebSocket 还按普通 HTTP 代理写法配置,导致握手失败
- 上传接口沿用默认 body size,生产一上线就 413
- 不分路径和接口类型,所有 location 使用同一套超时参数
proxy_pass路径拼接错误导致 404- 忘记关闭 WebSocket 的
proxy_buffering - 上游服务使用域名但不配置
resolver,DNS 变更后不生效
进阶路线
- 学习 Nginx
stream做 TCP 层代理 - 结合 Lua / OpenResty 做动态路由与鉴权
- 接入 active health check、熔断和限流模块
- 与 CDN、WAF、Ingress 形成分层流量治理体系
- 学习 Nginx 动态 upstream 配置(consul-template、confd)
- 研究 Nginx Ingress Controller 在 Kubernetes 中的应用
适用场景
- API 网关前置代理
- 前后端一体化部署入口
- WebSocket 服务入口
- 多实例应用的负载均衡与灰度发布
- 静态资源 CDN 回源代理
- 微服务 API 聚合网关
落地建议
- 每个反向代理站点都先明确:协议、路径规则、超时、日志字段
- 对 API、下载、上传、WebSocket 分别使用不同 location 策略
- 保持配置最小可读,复杂路由逻辑尽量拆文件管理
- 上线前用
curl、浏览器、WebSocket 客户端分别验证关键路径 - 为所有代理配置统一的日志格式,包含上游信息
- 建立配置模板和上线检查清单
排错清单
- 检查
proxy_pass路径拼接是否符合预期 - 检查后端是否正确识别
X-Forwarded-Proto/For/Host - 检查 502/504 是 Nginx 超时还是上游服务自身失败
- 检查上传限制、缓冲区、WebSocket 升级头是否配置完整
- 检查 upstream 中是否有节点被标记为 down
- 检查 DNS 解析是否正确(使用域名时)
- 检查防火墙是否放行 Nginx 到上游的端口
复盘问题
- 当前代理规则里,哪些路径应该走不同的超时和缓冲策略?
- 当后端切换为 HTTPS 或 WebSocket 时,现有配置是否还能工作?
- 如果某个上游节点持续慢响应,Nginx 是否有足够保护措施?
- 代理日志是否足够支撑你定位 4xx/5xx 来源?
- 灰度发布的流量比例是否能随时调整?是否有回滚方案?
