开篇:你为什么需要反向代理和负载均衡?

假设你只有一个后端服务,用户直接访问 http://your-backend:3000,一切正常。但突然用户变多了,一台服务器扛不住,你加了第二台,总不能让大家手动换地址吧?又或者你不想让用户直接接触你的后端 IP,想加一层缓存、限流、SSL 卸载?这时候 Nginx 就派上用场了。

打个比方,反向代理是给后端当“门卫”,所有请求先经过 Nginx,再由它转发给后端;负载均衡则是把请求分摊到多台后端服务器上,让系统更稳定、容量更大。

今天的教程我会直接给你能跑起来的配置,再聊几个我踩过的坑,最后给一些最佳实践。看完你就能自己搭一套生产可用的反向代理+负载均衡环境。

安装 Nginx(快速带过)

多数 Linux 发行版直接用包管理器装:

# Ubuntu / Debian sudo apt update && sudo apt install nginx -y # CentOS / RHEL sudo yum install epel-release -y sudo yum install nginx -y

装完启动:

sudo systemctl start nginx sudo systemctl enable nginx

检查是否运行:curl -I http://localhost 应该返回 200 或 304。

反向代理配置:一个最小可运行例子

现在假设你本地跑了一个 Node.js 服务,端口 3000。我们想通过 Nginx 把 /api 路径的请求都代理到它。

创建配置文件 /etc/nginx/conf.d/proxy.conf(或者直接在 sites-available 下写,随你习惯),内容如下:

server { listen 80; server_name your-domain.com; # 改成你的域名或IP location /api/ { proxy_pass http://127.0.0.1:3000/; # 注意结尾的斜杠 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; } location / { # 其他静态文件或直接返回 404 root /var/www/html; index index.html; } }

proxy_pass 后面如果加了斜杠 /,表示会把 /api 后面的路径拼接到后端地址上;如果不加斜杠,则会把整个 /api 路径原样传给后端,容易弄混。举个例子:

大多数情况你想去掉前缀,所以加斜杠。

测试配置:sudo nginx -t,没问题就 sudo systemctl reload nginx

现在你访问 http://your-domain.com/api/users 就会转发到本地的 http://127.0.0.1:3000/users

负载均衡配置:多台后端一起干活

假设你有两台 Node.js 服务,分别运行在 3001 和 3002 端口。把请求平均分到它们身上。

proxy.conf 里增加一个 upstream 块:

upstream backend_servers { # 默认轮询(round-robin) server 127.0.0.1:3001; server 127.0.0.1:3002; # 如果想加权,可以加 weight # server 127.0.0.1:3001 weight=3; # server 127.0.0.1:3002 weight=1; # 如果你需要根据客户端 IP 保持会话(session 粘滞),用 ip_hash # ip_hash; } server { listen 80; server_name your-domain.com; location / { proxy_pass http://backend_servers; # 引用 upstream 名称 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; } }

上面这个配置,所有请求都会轮流打到 3001 和 3002。如果你需要保持用户 session(比如登录状态存后端内存里),就用 ip_hash,这样同一 IP 的请求始终打到同一台后端。但要注意,如果后端挂了,ip_hash 会导致流量集中到别的机器,Nginx 会自己处理 failover。

完整可运行示例:我把上面两个配置合并成一个文件,再加一点健康检查和超时设置:

upstream backend_servers { # 轮询 + 加权 server 127.0.0.1:3001 weight=2 max_fails=3 fail_timeout=30s; server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s; # 启用 keepalive 连接池,减少后端连接建立开销 keepalive 32; } server { listen 80; server_name api.example.com; # 日志格式建议带上 upstream 信息,方便排查 access_log /var/log/nginx/api_access.log upstreamlog; error_log /var/log/nginx/api_error.log warn; location / { proxy_pass http://backend_servers; # 超时设置(单位秒) proxy_connect_timeout 5; proxy_send_timeout 10; proxy_read_timeout 10; # 传递客户端真实 IP proxy_set_header Host $http_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; # 如果后端返回 500/502 等,尝试下一个后端 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; proxy_next_upstream_tries 2; } } # 自定义日志格式(放在 http 块或本 server 块中) # 如果放在 server 块内,记得要先定义 log_format log_format upstreamlog '$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';

把这段保存为 /etc/nginx/conf.d/api.conf,然后 nginx -t 检查,接着 systemctl reload nginx

后端服务可以用简单的 Express 来测试:

// server1.js (port 3001) const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('Hello from server 1, Host: ' + req.headers.host); }); app.listen(3001); // server2.js (port 3002) // 内容类似,端口改为 3002

启动两个服务,然后多次访问 http://api.example.com,你会看到轮流返回 server 1server 2

常见错误与解决方法

错误1:502 Bad Gateway

报错:浏览器返回 502,Nginx 错误日志显示 connect() failed (111: Connection refused) while connecting to upstream

原因:后端服务没启动,或者 Nginx 配置的 upstream 地址写错了(比如端口不对、IP 写成了 localhost 但实际监听 127.0.0.1 却配了 0.0.0.0 等)。

解决

错误2:404 Not Found 或返回了 Nginx 默认页面

报错:请求 /api/users 返回 404,或者看到的是 Nginx 欢迎页。

原因location 匹配顺序问题,或者 proxy_pass 没有正确剥离路径前缀。

解决

错误3:后端获取不到客户端真实 IP

现象:后端看到的 req.ip 总是 127.0.0.1 或 Nginx 的 IP。

原因:没有设置 proxy_set_header X-Real-IPX-Forwarded-For

解决:加上上面示例中的那几个 header。另外后端框架可能需要从这些 header 中读取真实 IP。比如 Express 可以安装 trust-proxy 中间件:

app.set('trust proxy', true); // 告诉 Express 信任 Nginx 传来的 X-Forwarded-* 头

实际案例心得:过来人的经验

http { resolver 8.8.8.8 valid=30s; # 30秒重新解析 server { set $backend_upstream "my-service.example.com:80"; location / { proxy_pass http://$backend_upstream; } } }

最佳实践建议

- 不要将 Nginx 直接暴露在互联网上而不做限制。至少加上 limit_req 模块防止 CC 攻击。

- 如果对外暴露的是 HTTPS,记得在 server 块配置 SSL,然后 proxy_pass 给后端的 HTTP。Nginx 负责 SSL 终结,减轻后端压力。

- 设置合理的 proxy_read_timeout 避免后端慢请求拖垮 Nginx 工作进程。

- 使用自定义日志格式记录 upstream 信息,排查问题时你会感谢自己。

- 配置 error_log 级别为 warn 以上,避免生产日志爆炸。

- 配合 Prometheus + nginx-lua-prometheus 或 ngx_http_stub_status_module 监控请求量。

- 调整 worker_processes 为 auto,worker_connections 根据内存适当加大(如 1024或2048)。

- 启用 sendfile 和 tcp_nopush。

- 如果后端返回大量静态文件,可以考虑在 Nginx 层加缓存:proxy_cache

- 利用 upstream 的两个 server,先下线一个、更新、再上线;或者通过 weight=0 临时摘除节点。

- 更高级的可以用 $cookie_backend$http_x_canary 变量动态路由到不同 upstream。

总结

Nginx 反向代理和负载均衡的配置其实不复杂,核心就是 upstream + proxy_pass。但细节决定成败:斜杠、IP 地址写法、超时、header 传递、DNS 缓存……每一个小点都可能让你抓狂几小时。

今天给的示例你直接复制改一改就能用。建议先在本机用 Docker 起两个简单的 HTTP 服务测试,把配置调通后再上生产。

调试技巧:配置完成后先用 nginx -t 检查语法,然后用 curl -v http://你的地址 看响应头,确认请求是否正确转发。502错误通常是后端服务没启动或地址写错,504是超时问题。

最后,如果你用的是 HTTPS 站点,记得把 listen 80 改成 listen 443 ssl,再加几行 SSL 配置。代理到后端时,如果后端也是 HTTPS,则 proxy_pass 要写 https://...,并注意证书验证。这部分可以单独写一篇,今天就不展开了。

好了,动手试试吧。遇到问题多看 error.log,真相就在那里。