Nginx 为什么适合做反向代理
当一个网站从“一台服务器跑一个服务”长到多台实例、多种服务时,最早暴露的问题就是入口。用户的请求总得先到一个固定地址,再由它决定转发给谁——这个入口角色,Nginx 是最常见的承担者。
Nginx 采用事件驱动、异步非阻塞的架构,用少量 worker 进程就能扛住上万并发连接,内存占用还低。相比“每个请求占一个线程”的传统服务器,它特别适合三件事:静态资源服务、反向代理和负载均衡。
反向代理指代理服务器站在服务端一侧,代表后端服务接收外部请求。客户端并不知道真正处理请求的是哪台机器,它只看到一个统一入口。这样做的好处是多方面的:
- 隐藏后端拓扑,对外只暴露一个域名和端口,扩容、迁移都不影响客户端。
- 统一做 TLS 终止,证书只在入口维护一份,后端服务专注业务。
- 集中实现缓存、压缩、限流、跨域与访问日志,避免每个服务重复造轮子。
- 支持灰度与蓝绿发布:把一部分流量按权重或规则导到新版本。
- 反向代理proxy_pass 把请求转发到后端,并用 proxy_set_header 传递真实客户端信息。
- upstream把多台后端组成一个上游组,用轮询、权重、IP 哈希等策略分流。
- 故障转移proxy_next_upstream 让某台后端异常时自动切到下一台。
- 长连接keepalive 复用与后端的连接,显著降低 TIME_WAIT 与延迟。
正向代理与反向代理的区别
“代理”这个词容易混淆,关键在于它代表谁。正向代理代表客户端,反向代理代表服务端。前者需要客户端显式配置,后者对客户端完全透明。
| 维度 | 正向代理 | 反向代理 |
|---|---|---|
| 代表对象 | 客户端 | 服务端 |
| 部署位置 | 靠近客户端 | 靠近服务端 |
| 客户端是否感知 | 需要显式配置代理地址 | 无感知,只看到统一入口 |
| 隐藏对象 | 隐藏客户端真实 IP | 隐藏后端服务器拓扑 |
| 典型用途 | 企业出口、爬虫出口、访问控制 | 负载均衡、网关、TLS 终止 |
理解了这层区别,就能明白为什么“给后端加 Nginx”是最常见的架构动作:它是服务端的门面,客户端的每一次请求都先到这里,再由它分发。
安装与配置文件组织
在主流 Linux 发行版上安装 Nginx 只需要一条命令。装完后先确认版本与编译参数,尤其是是否带 SSL 模块,避免后面配 HTTPS 时才发现不支持。
# 以 Ubuntu / Debian 为例
sudo apt update
sudo apt install -y nginx
nginx -v # 查看版本号
nginx -V # 查看编译参数,确认包含 --with-http_ssl_module
nginx -t # 校验配置文件语法,改配置后必做
sudo systemctl enable --now nginx
sudo systemctl reload nginx # 平滑重载,不中断已有连接
配置文件的组织方式值得先说清楚,否则很容易改错地方。主配置 /etc/nginx/nginx.conf 里通过 include 引入了各站点配置;业务配置建议单独放 /etc/nginx/conf.d/*.conf,每个站点一个文件。这样既方便版本管理,也方便按需启用或禁用。
# /etc/nginx/nginx.conf 中的关键片段
worker_processes auto; # 通常等于 CPU 核心数
events {
worker_connections 10240; # 单个 worker 允许的最大连接数
}
http {
include mime.types;
include /etc/nginx/conf.d/*.conf; # 站点配置都放这里
sendfile on;
keepalive_timeout 65;
}
最小可用的反向代理配置
下面是一个可以直接放进 /etc/nginx/conf.d/app.conf 的完整示例:把 api.example.com 的请求转发到两台后端实例上。
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
server {
listen 80;
server_name api.example.com;
location / {
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_set_header。默认情况下,Nginx 转发时会把 Host 改成上游名字,后端拿到的客户端 IP 也变成 Nginx 的地址。结果就是:后端拼接绝对地址做重定向时出错、日志里全是同一个来源 IP、按 IP 做的限流失效。把 Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto 显式传下去,是反向代理的第一条军规。
upstream 负载均衡策略
Nginx 内置了多种负载均衡算法,不需要额外插件。选哪一种,取决于后端是否为无状态、实例规格是否一致、请求耗时是否均匀。
| 策略 | 指令写法 | 适用场景 |
|---|---|---|
| 轮询(默认) | server a; server b; | 实例规格一致、无状态服务 |
| 权重 | server a weight=3; | 混合规格、灰度发布 |
| IP 哈希 | ip_hash; | 需要会话保持的有状态服务 |
| 最少连接 | least_conn; | 请求耗时差异较大 |
| 一致性哈希 | hash $request_uri consistent; | 提高缓存命中率 |
下面这个 upstream 组把权重、备用节点和长连接组合在一起,是生产环境里很常见的写法:
upstream app_backend {
least_conn; # 优先选连接数最少的实例
server 10.0.0.11:8080 weight=3; # 权重 3,承接更多流量
server 10.0.0.12:8080 weight=1;
server 10.0.0.13:8080 backup; # 备用节点,主节点全部不可用时启用
keepalive 32; # 与后端保持 32 条空闲长连接
}
要注意:一旦启用了 keepalive,location 里还需要补上 proxy_http_version 1.1; 与 proxy_set_header Connection "";,否则长连接无法真正复用。权重适合做小流量灰度,比如先给新版本 10% 的流量观察一阵,再逐步放大。
健康检查与故障转移
负载均衡的另一半是容错:当某台后端出问题时,流量要能自动绕开它。开源版 Nginx 提供的是被动健康检查——只有当请求失败时才会把实例标记为不可用,max_fails 与 fail_timeout 控制判定阈值。
upstream app_backend {
server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
}
location / {
proxy_pass http://app_backend;
proxy_connect_timeout 2s; # 连接后端超时
proxy_read_timeout 10s; # 读取响应超时
# 命中这些错误时,自动切到下一个上游实例
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2; # 最多尝试 2 个实例
proxy_next_upstream_timeout 5s; # 总重试时间上限
}
这里有一个必须提醒的坑:proxy_next_upstream 默认不包含非幂等请求的重试语义,对 POST 盲目重试可能造成重复下单。因此它更适合配置在查询类接口上;写接口的重试,应当交给带幂等键的业务逻辑处理,而不是交给网关。
HTTPS、压缩与静态缓存
把 TLS 终止放在 Nginx 上,是架构上最常见的做法:证书只需要在入口维护,后端服务可以继续用明文 HTTP 在内网通信。下面这段配置同时打开了 HTTPS、HTTP/2、gzip 压缩和静态资源强缓存。
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/example.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
ssl_protocols TLSv1.2 TLSv1.3;
gzip on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript;
location ~* \.(jpg|png|css|js)$ {
root /var/www/static;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
}
}
压缩只对文本类内容有意义,图片、视频本身已经是压缩格式,再 gzip 只是白白消耗 CPU。带哈希文件名的前端静态资源可以放心设置长缓存;入口页这类会被频繁替换的文件则应设置较短缓存或使用协商缓存,避免用户拿到旧版本。
常见坑与性能调优
把 Nginx 用稳,除了会写配置,更重要的是知道它会在哪些地方悄悄出问题。下面这些坑几乎每个团队都踩过至少一次:
- worker_connections 不够:日志里出现 “worker_connections are not enough”,通常是并发上来了但没同步调大
worker_connections与系统ulimit -n。 - 忘记传 Host:后端生成的重定向地址或签名校验出错,排查半天才发现是 Host 被改写。
- 没开长连接:每次请求都新建到后端的连接,机器上堆满 TIME_WAIT,延迟也居高不下。
- 改完不校验:直接 reload 一个语法错误的配置会导致重载失败;养成先
nginx -t再 reload 的习惯。 - 超时一刀切:把
proxy_read_timeout设得过短会误杀慢接口,过长又会让故障实例拖住整个上游组,应按接口类型分别设置。
调优的顺序建议是:先看访问日志与错误日志定位瓶颈,再调连接与超时参数,最后才考虑缓存与压缩。盲目加大数值往往只是把问题往后推,而日志永远是最诚实的证据。
Nginx 的价值不在于配置项多,而在于它足够稳:把入口这一层的转发、缓存与容错做扎实,后端服务就能更专注地处理业务逻辑。