前言:为什么你需要 Nginx 反向代理?
如果你手里只有一台 VPS,却要跑 Node.js、Python Flask、Go 服务、甚至一个静态博客——每个服务都想独占 80/443 端口,怎么办?
答案就是 Nginx 反向代理。它像一个总机接线员,站在服务器门口,根据请求的域名或路径,把流量分发给背后不同的服务。有了它,一台机器可以优雅地承载十几个应用。
更进一步,负载均衡让你在多个后端实例之间分配流量——当你把应用从单机扩到集群时,Nginx 就是第一道关卡。
这篇文章会带你从零开始,在 Ubuntu 24.04 上配置 Nginx 反向代理,再一步步升级到负载均衡、健康检查、Session 保持等生产级能力。先看看你已有的 VPS 安全加固 做好了没有——SSH 和防火墙是前置条件。
环境准备
假设你有一台 Ubuntu 24.04 VPS,上面跑了两个后端服务:
- App A:一个 Flask 应用,监听
127.0.0.1:5000 - App B:一个 Express 应用,监听
127.0.0.1:3000
目标:通过 Nginx 把 app-a.example.com 的请求转发给 Flask,app-b.example.com 转发给 Express。
$ curl http://127.0.0.1:5000
{"message": "Hello from Flask!", "port": 5000}
$ curl http://127.0.0.1:3000
{"message": "Hello from Express!", "port": 3000}
第一步:安装 Nginx
Ubuntu 24.04 的官方源已经收录了 Nginx 1.24,直接安装即可:
$ sudo apt update && sudo apt install nginx -y
确认安装成功:
$ nginx -v
nginx version: nginx/1.24.0 (Ubuntu)
$ sudo systemctl status nginx
● nginx.service - A high performance web server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled)
Active: active (running) since Mon 2026-06-08 08:00:00 UTC
浏览器访问 VPS 的 IP 地址,能看到 Nginx 默认欢迎页就说明跑起来了。
如果你还没在 VPS 上装 Nginx,建议同时配置好 SSL。可以看看这篇 Let’s Encrypt SSL 证书自动续期指南,几步搞定 HTTPS。
第二步:反向代理实战 — 多域名分发
核心思路很简单:Nginx 读取请求头中的 Host 字段,匹配 server_name,把流量 proxy_pass 到对应的后端。
创建第一个站点的配置文件:
$ sudo vim /etc/nginx/sites-available/app-a
server {
listen 80;
server_name app-a.example.com;
location / {
proxy_pass http://127.0.0.1:5000;
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_pass:把请求转发到这个地址
- X-Real-IP / X-Forwarded-For:把客户端真实 IP 传给后端,否则后端只能看到 Nginx 的 IP(127.0.0.1)
- X-Forwarded-Proto:告诉后端原始请求用的是 HTTP 还是 HTTPS
同样创建 App B 的配置并启用:
$ sudo vim /etc/nginx/sites-available/app-b
$ sudo ln -s /etc/nginx/sites-available/app-a /etc/nginx/sites-enabled/
$ sudo ln -s /etc/nginx/sites-available/app-b /etc/nginx/sites-enabled/
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
验证效果:
$ curl -H "Host: app-a.example.com" http://127.0.0.1/
{"message": "Hello from Flask!", "port": 5000}
$ curl -H "Host: app-b.example.com" http://127.0.0.1/
{"message": "Hello from Express!", "port": 3000}
同一个 80 端口,根据 Host 头分发不同请求——反向代理的核心价值在这里体现得淋漓尽致。
第三步:负载均衡 — 从单机到集群
当你的 Flask 应用流量上来,单进程 5000 端口扛不住了,想跑两个实例分担压力:
$ gunicorn -w 1 -b 127.0.0.1:5001 app:app &
$ gunicorn -w 1 -b 127.0.0.1:5002 app:app &
$ curl http://127.0.0.1:5001
{"message": "Hello from Flask!", "port": 5001}
$ curl http://127.0.0.1:5002
{"message": "Hello from Flask!", "port": 5002}
在 Nginx 中定义一个 upstream 块,把两个实例放进一个组里,然后用 proxy_pass 指向这个组:
upstream flask_cluster {
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
server {
listen 80;
server_name app-a.example.com;
location / {
proxy_pass http://flask_cluster;
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)策略——请求轮流分配给每个后端。测试一下:
$ for i in $(seq 1 6); do
curl -s -H "Host: app-a.example.com" http://127.0.0.1/ | jq .port
done
5001
5002
5001
5002
5001
5002
完美!6 个请求均匀分给了两个后端。
第四步:负载均衡策略进阶
默认轮询不总是最优解。Nginx 支持多种策略,一行配置就能切换:
加权轮询(Weighted Round Robin)
服务器性能不均时,给更强的机器多分配请求:
upstream flask_cluster {
server 127.0.0.1:5001 weight=3; # 这台机器性能强,分 3 份
server 127.0.0.1:5002 weight=1; # 这台弱,分 1 份
}
IP Hash(Session 保持)
有些应用把 session 存在本地内存,换后端就会丢失登录状态。IP Hash 保证同一客户端 IP 始终落到同一后端:
upstream flask_cluster {
ip_hash;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
⚠️ 注意:ip_hash 不能和 weight 同时使用。另外如果用户通过同一个公司 NAT 出口访问,所有同事都会被分配到一个后端——真实生产环境建议用 Redis 集中存 Session 配合 sticky。
最少连接数(Least Connections)
请求处理时间差异大时(有的接口 10ms,有的 2s),轮询可能导致慢后端堆积请求。最少连接数把新请求发给当前活动连接最少的那台:
upstream flask_cluster {
least_conn;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
第五步:健康检查 — 自动踢掉挂了的后端
后端挂了怎么办?Nginx 可以主动探测,发现不健康的节点就自动从池子里移除:
upstream flask_cluster {
server 127.0.0.1:5001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:5002 max_fails=3 fail_timeout=30s;
server 127.0.0.1:5003 backup; # 备用节点,其他全挂时才启用
}
- max_fails=3:连续失败 3 次后标记为不可用
- fail_timeout=30s:标记为不可用后等待 30 秒再尝试恢复
- backup:备用服务器,所有主节点挂了才顶上
测试一下——故意停掉 5001:
$ kill %1 # 停掉 5001
$ for i in $(seq 1 6); do
curl -s -H "Host: app-a.example.com" http://127.0.0.1/ | jq .port
done
5002 # Nginx 自动跳过了挂掉的 5001
5002
5002
5002
5002
5002
重新启动后端,30 秒后 Nginx 会自动重新检测并恢复。这种”自愈”能力是生产环境的基础。
第六步:WebSocket 代理
如果你的应用用 WebSocket(聊天室、实时推送、协同编辑),需要额外告诉 Nginx 升级连接协议:
location /ws/ {
proxy_pass http://flask_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s; # WebSocket 长连接不要超时
}
关键点:proxy_http_version 1.1 和 Connection "upgrade" 必须同时设置,缺一个 WebSocket 就握手失败。
第七步:生产环境加固建议
以上配置能用,但上生产前还需要几个优化:
1. 缓冲调优
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
缓冲可以减少后端连接占用,尤其对大文件下载效果明显。
2. 超时配置
proxy_connect_timeout 5s; # 连接后端超时
proxy_send_timeout 30s; # 向后端发送数据超时
proxy_read_timeout 30s; # 从后端读取响应超时
合理超时防止慢后端拖死 Nginx worker。
3. 限流保护
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
location / {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://flask_cluster;
}
每秒每个 IP 最多 10 个请求,突发允许 20 个。防止单个客户端打爆后端。
4. 配合 SSL
HTTPS 终端化(SSL Termination)是反向代理的经典用法——证书装在 Nginx 上,后端走 HTTP 就行:
server {
listen 443 ssl;
server_name app-a.example.com;
ssl_certificate /etc/letsencrypt/live/app-a.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app-a.example.com/privkey.pem;
location / {
proxy_pass http://flask_cluster;
proxy_set_header X-Forwarded-Proto https;
}
}
SSL 证书的管理可以交给 Certbot 自动续期,配合 crontab 定时任务 每月检查一次就高枕无忧了。
常见问题(FAQ)
Q: Nginx 反向代理后,后端拿不到真实客户端 IP 怎么办?
默认情况下,后端看到的 REMOTE_ADDR 是 Nginx 的 IP(如 127.0.0.1)。需要两件事:① Nginx 配置中加上 proxy_set_header X-Real-IP $remote_addr; 和 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;;② 后端框架需要读取这些头——Flask 用 request.headers.get('X-Real-IP'),Express 用 req.headers['x-real-ip']。如果你用 Gunicorn,加上 --forwarded-allow-ips="*" 参数。
Q: upstream 里加了多个 server,但流量好像都去了第一个,怎么回事?
检查是不是用了 ip_hash 策略——它会基于客户端 IP 哈希固定路由,测试时如果一直从同一个 IP(本机 127.0.0.1)发请求,自然会全落到同一台后端。换不同 IP 测试,或者临时改成默认轮询看是否均匀分配。另一个可能:keepalive 连接池设置过大,导致连接复用绕过了负载分配——确认 keepalive 的值不要超过后端实例数。
Q: Nginx 反向代理和 Cloudflare Tunnel / frp 有什么区别?
Nginx 反向代理运行在你自己的 VPS 上,适合「有公网 IP 的服务器内部做流量分发」。Cloudflare Tunnel 和 frp 解决的是另一个问题——「没有公网 IP 的内网机器如何被外部访问」——它们通过穿透/隧道把请求从公网入口转发到内网。两者不冲突:你可以用 Cloudflare Tunnel 把流量引入 VPS,然后在 VPS 上用 Nginx 做二级分发和负载均衡。
总结
Nginx 反向代理是运维的基本功。一个配置文件,把多域名分发、负载均衡、健康检查、SSL 终端化、WebSocket 代理全搞定。配合 VPS 安全加固、SSL 自动续期、crontab 定时任务,你就有了一个完整的生产级部署链条。
下一步可以研究 Docker + Nginx 的组合——用 Docker Compose 一键拉起多服务集群,Nginx 容器做统一入口。这个留到下次讲。
🔗 相关阅读:凌晨3点被PagerDuty叫醒:一次Nginx 502故障排查全过程实录 — 配置好了反向代理,502故障排查也得会,总有一天用得上。