把一个 HTTP 端口反代出来很简单,三行 proxy_pass 就能跑起来。但在真实的生产环境中,很快就会遇到各种实际问题:套了 Cloudflare CDN 之后后端日志全显示 CDN 节点的 IP,查不出真实访客;跑实时聊天、SSH Web 终端或智能群管机器人时,由于缺少必要的 Upgrade 请求头导致 WebSocket 频繁断连;或者升级到新版 Nginx 后旧的 http2 on; 语法疯狂报废弃警告。

这篇笔记记录生产环境中 Nginx 反向代理必配的几个核心配置项:WebSocket 双向代理、Cloudflare 真实客户端 IP 穿透、现代 HTTP/2 语法,以及一份开箱即用的配置模板。


生产反向代理常见的几个深水区坑点

基础反代只是简单的“端口转接”,而生产环境面临复杂的外部 CDN、长连接协同与网络合规:

【外界访客 (114.x.x.x)】
        │
        ▼ 发起 HTTPS / WSS 请求
┌───────────────────────────────────────────────────────────┐
│  ☁️ Cloudflare 全球 Anycast CDN 节点 (172.64.x.x)           │
└───────────────────────────────────────────────────────────┘
        │
        ▼ 携带 CF-Connecting-IP 请求头穿透
┌───────────────────────────────────────────────────────────┐
│  🏢 你的生产 Nginx 网关                                    │
│                                                           │
│   🔍 真实 IP 还原模块:识别 CF 官方 IP 段,剔除代理伪装,    │
│      将真正的客户端 IP (114.x.x.x) 还原并交由后端应用记录; │
│   ⚡ WebSocket 动态协议升级(map 机制拒绝死连);          │
│   🚀 HTTP/2 现代流式并发多路复用(采用 http2 on; 语法); │
│   🛡️ 跨域 CORS 精准白名单校验。                           │
└───────────────────────────────────────────────────────────┘

准备工作

  • [ ] Nginx 已处于运行状态:执行 nginx -v 建议为 1.20+ 或 1.24+ 较新版本;
  • [ ] 后端长连接或 API 服务就绪:跑在本地特定端口(如 127.0.0.1:8080);
  • [ ] 已理解草稿箱与生效箱机制:改动永远在 /etc/nginx/sites-available/ 进行。

配置 WebSocket 动态升级转发

很多教程教小白直接在 location 里写死:

proxy_set_header Connection "upgrade"; # ❌ 严重反模式!
  • 🗣️ 死穴解析:如果客户端发来的是普通 HTTP 请求(并没有要求 Upgrade),你却强行告诉后端“要升成 WebSocket”,很多后端(如 Node.js、Python FastAPI、Go)会当场报协议解析错误!

✅ 官方工业级正解:利用 map 动态握手!

服务器端: 打开 /etc/nginx/nginx.conf,在 http { ... } 内部放入这段全局动态映射(如果系统没有的话):

# 检查是否已存在 map 规则,没有则自包含幂等写入
if ! grep -q "connection_upgrade" /etc/nginx/nginx.conf 2>/dev/null; then
    sed -i '/http {/a \    map $http_upgrade $connection_upgrade {\n        default upgrade;\n        ""      close;\n    }' /etc/nginx/nginx.conf
    echo "[✓] 全局 WebSocket 动态映射已写入!"
fi
  • 🗣️ 大白话:当访客发来 WebSocket 升级头时,Connection 设为 upgrade;如果是普通网页请求,Connection 设为 close。聪明自适应,永远不报错!

还原真实访客 IP(穿透 Cloudflare CDN)

当你套了 Cloudflare CDN 后,如果没有做还原,你的所有后端访问日志、防刷限流、风控系统里记录的全是 Cloudflare 节点的机房 IP(如 172.64.x.x 或 104.28.x.x),导致 Fail2ban 误封 CDN。

我们需要告诉 Nginx:这些 IP 属于受信代理,真实 IP 放在 CF-Connecting-IP 报头里!

服务器端: 一键写入 Cloudflare 官方 IPv4/IPv6 受信网段配置:

cat << 'EOF' > /etc/nginx/conf.d/cloudflare-real-ip.conf
# Cloudflare 官方 IPv4 网段
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;

# Cloudflare 官方 IPv6 网段
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

# 指定通过 CF 官方头还原真实访客 IP
real_ip_header CF-Connecting-IP;
EOF
  • ✅ 成功标志:此配置落地后,你的 $remote_addr 将奇迹般地变回访客手机或家庭宽带的真实 IP,防爆破与访问统计瞬间恢复精准!

适配新版 Nginx 的 HTTP/2 监听语法

在老教程里,很多教程写:

listen 443 ssl http2; # ❌ Nginx 1.25.1+ 已正式废弃此语法!

如果你在新版系统(Debian 12+ 或 Ubuntu 24.04)上这么写,运行 nginx -t 会疯狂刷黄字:
nginx: [warn] the "http2" directive is deprecated, use the "http2" directive instead。

✅ 现代化标准的 HTTP/2 写法:

listen 443 ssl;
listen [::]:443 ssl;
http2 on; # 🟢 官方规范新语法,清爽独立!

开箱即用的完整反代站点模板

现在我们将上面的所有生产特性(真实 IP 还原、动态 WebSocket、HTTP/2、大文件放行、超时保护)融为一体,生成一份真正无可挑剔的站点配置。

服务器端: 先填写你的配置变量:

MY_DOMAIN="api.0000996.xyz"
UPSTREAM_PORT="8080"

接着,整段执行自包含防呆写入脚本:

cat << EOF > /etc/nginx/sites-available/${MY_DOMAIN}.conf
server {
    listen 80;
    listen [::]:80;
    server_name ${MY_DOMAIN};

    # 生产安全:放行 100MB 附件与大文件上传
    client_max_body_size 100M;
    client_body_buffer_size 128k;

    location / {
        proxy_pass http://127.0.0.1:${UPSTREAM_PORT};

        # 核心请求头透传
        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 升级(依赖第 1 步的 map 规则)
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection \$connection_upgrade;

        # 生产级超时与缓冲调优(防止长连接被提前中断)
        proxy_connect_timeout 60s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;

        # 开启缓冲支持(高并发吞吐优化)
        proxy_buffering on;
        proxy_buffers 16 32k;
        proxy_busy_buffers_size 64k;
    }
}
EOF

# 激活站点软链接
ln -sf /etc/nginx/sites-available/${MY_DOMAIN}.conf /etc/nginx/sites-enabled/

# 黄金自检
nginx -t && systemctl reload nginx
  • ✅ 成功标志:终端打印出 syntax is ok 与 test is successful,服务平滑重载完毕!

常见报错排查

异常现场 真实根因 对应一秒救命招式
nginx -t 报 unknown "connection_upgrade" variable 漏掉了第 1 步在 nginx.conf 里的 map 定义 重新执行第 1 步脚本,确保 map 规则位于 http { ... } 内部。
反向代理访问报 504 Gateway Time-out 后端处理耗时超过了默认的 60 秒限制 将配置里的 proxy_read_timeout 调大至 300s 或 600s,重载生效。
套了 Cloudflare 后日志仍显示国外 IP 未安装 http_realip_module 或未配置 real_ip_header 执行 nginx -V 2>&1 | grep realip 确认模块存在,并核实第 2 步的网段配置文件已加载。
浏览器控制台报 CORS header missing 跨域错误 前后端域名不同,未配置允许跨域标头 在 location 下加入 add_header Access-Control-Allow-Origin *;(生产建议填具体主域)。