一、我与Nginx的"相爱相杀"
作为一名后端开发,我第一次接触Nginx是在实习的时候。那时候我以为Nginx就是一个"配置起来很简单"的文件服务器,用来放静态页面和做反向代理。
后来真正用了才发现,Nginx的水深着呢:
- upstream里配置错误,排查了半天发现是IP写错了
- 限流配置不起作用,原来是放在了错误的location里
- 长连接配置不对,导致大量连接处于TIME_WAIT状态
- 缓存配置了一直没生效,原来需要开启proxy_cache_path
- 配置reload后请求成功率下降,找了半天发现是worker进程数设置有问题
这些坑一个一个踩过来,我才算真正理解了Nginx。今天把我这些年积累的Nginx经验整理成文,希望能帮你少走弯路。
二、Nginx基础架构:先理解它是怎么工作的
2.1 Nginx的工作模型
Nginx采用多进程 + 事件驱动的架构:
┌─────────────┐
│ Master进程 │
│ (管理进程) │
└──────┬──────┘
│ 监控/热加载
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Worker 1 │ │ Worker 2 │ │ Worker 3 │
│ (工作进程)│ │ (工作进程)│ │ (工作进程)│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────────────┼──────────────────┘
▼
┌─────────────────────┐
│ 共享:连接池/缓存 │
│ 监听:所有worker │
│ 共享同一端口 │
└─────────────────────┘
Master进程职责:
- 读取和验证配置文件
- 管理worker进程(启动/停止/重启)
- 接收外部信号(如reload、stop)
- 热加载配置(不停服更新配置)
Worker进程职责:
- 处理实际的请求
- 每个worker都是独立进程,不会相互阻塞
- 争抢accept()锁,高效处理并发
2.2 为什么Nginx性能这么高
秘密1:异步非阻塞I/O
Nginx使用epoll(Linux)、kqueue(FreeBSD)等高效I/O多路复用机制,一个worker可以同时处理成千上万个连接。
nginx
# worker进程数配置
worker_processes auto; # 自动等于CPU核心数
# 每个worker允许的连接数
worker_connections 65535;
# 使用epoll
use epoll;
# 开启高效异步模式
aio on;
thread_pool default threads=32 max_queue=65535;
秘密2:惊群问题的解决
当新连接到达时,多个worker都会被唤醒去抢这个连接,这就是"惊群"问题。Nginx通过accept_mutex锁来解决:
nginx
# 开启accept互斥锁(默认开启)
accept_mutex on;
# 惊群等待时间
accept_mutex_delay 500ms;
秘密3:CPU亲和性绑定
将worker绑定到特定CPU核心,减少进程切换开销:
nginx
worker_cpu_affinity auto; # 自动绑定到各CPU核心
# 手动绑定(4核机器)
worker_cpu_affinity 0001 0010 0100 1000;
2.3 配置文件结构
nginx
# 全局块:影响nginx整体运行
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
# events块:影响nginx与用户的连接
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
# http块:HTTP服务器相关配置
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# 开启高效文件传输
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# keepalive连接
keepalive_timeout 65;
keepalive_requests 10000;
# gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript;
# 上游服务器组
upstream backend {
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 backup; # 备用服务器
keepalive 32; # 长连接数
}
server {
# server块:虚拟主机配置
listen 80;
server_name example.com;
location / {
proxy_pass http://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;
}
}
}
三、Upstream负载均衡策略:选择合适的分发方式
3.1 轮询(Round Robin)
默认策略,按顺序依次分配请求。
nginx
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
3.2 加权轮询(Weighted Round Robin)
根据权重比例分配请求,适合服务器性能不同的场景。
nginx
upstream backend {
# 机器1配置高,权重3
server 192.168.1.10:8080 weight=3;
# 机器2配置低,权重2
server 192.168.1.11:8080 weight=2;
# 机器3配置中等,权重2
server 192.168.1.12:8080 weight=2;
}
3.3 IP哈希(IP Hash)
根据客户端IP计算哈希值,保证同一IP的请求总是发到同一台服务器。
nginx
upstream backend {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
适用场景: 需要会话保持,但session存储在单机内存中的场景。
注意: 如果某台服务器宕机,其IP哈希会被重新分配,可能导致会话丢失。
3.4 最少连接(Least Connections)
将请求分配给当前连接数最少的服务器。
nginx
upstream backend {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
适用场景: 请求处理时间差异较大的场景。
3.5 哈希(Hash)
通用哈希,可基于任意key进行哈希。
nginx
upstream backend {
hash $request_uri consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
consistent 参数开启一致性哈希,服务器增减时只会影响少量请求。
3.6 服务器状态设置
nginx
upstream backend {
server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 backup; # 备用服务器
server 192.168.1.13:8080 down; # 已下线
}
| 参数 | 说明 |
|---|---|
| weight | 权重 |
| max_fails | 最大失败次数,超过后标记为不可用 |
| fail_timeout | 失败后多久重新尝试(默认10秒) |
| backup | 备用服务器,平时不接请求 |
| down | 标记为永久不可用 |
四、location匹配规则:搞清楚请求去哪了
4.1 匹配优先级
Nginx的location匹配优先级是一个经典难点:
1. 精确匹配 location = / {}
2. 正则匹配 location ~ /images/ {}
3. 前缀匹配 location ^~ /images/ {}
4. 普通匹配 location / {}
优先级从高到低:
匹配优先级(从高到低):
= 精确匹配 (完全相等,优先级最高)
^~ 前缀匹配 (匹配后不再检查正则)
~ 正则匹配 (按配置顺序,第一个匹配为准)
~* 不区分大小写正则
/ 通用匹配 (兜底)
4.2 配置示例
nginx
server {
listen 80;
server_name example.com;
# 1. 精确匹配 - 只匹配 /
location = / {
return 200 "精确匹配: /\n";
}
# 2. 正则匹配 - /api开头的请求
location ~ ^/api/ {
return 200 "正则匹配: /api/\n";
}
# 3. 前缀匹配 - /images开头的请求
location ^~ /images/ {
return 200 "前缀匹配: /images/\n";
}
# 4. 普通匹配 - 所有请求
location / {
return 200 "通用匹配: /\n";
}
}
4.3 实际业务配置
nginx
server {
listen 80;
server_name example.com;
# 静态资源 - 长期缓存
location ~* \.(jpg|jpeg|png|gif|ico|webp|svg)$ {
root /var/www/static;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# CSS/JS - 中期缓存
location ~* \.(css|js|woff|woff2|ttf)$ {
root /var/www/static;
expires 7d;
add_header Cache-Control "public, max-age=604800";
}
# API接口 - 不缓存
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 管理后台 - 需要认证
location ^~ /admin/ {
proxy_pass http://admin_backend;
# 认证逻辑
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
# 默认处理
location / {
proxy_pass http://frontend;
}
}
五、反向代理核心配置:让Nginx真正为你服务
5.1 最基本的代理配置
nginx
location / {
proxy_pass http://backend;
}
这看起来很简单,但背后发生的事情可不简单:
客户端请求 → Nginx → 代理连接到后端 → 发送请求 → 接收响应 → 返回给客户端
5.2 必须传递的头信息
nginx
location / {
proxy_pass http://backend;
# 传递真实IP
proxy_set_header Host $host; # 原始Host
proxy_set_header X-Real-IP $remote_addr; # 真实客户端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链IP
proxy_set_header X-Forwarded-Proto $scheme; # 原始协议(http/https)
proxy_set_header X-Forwarded-Host $host; # 原始主机
proxy_set_header X-Forwarded-Port $server_port; # 原始端口
}
为什么这些头重要?
没有这些配置,后端应用获取到的IP都是Nginx的IP,无法区分真实用户,也无法做IP限流、日志分析等。
5.3 超时配置
nginx
location / {
proxy_pass http://backend;
# 连接超时
proxy_connect_timeout 60s;
# 发送请求超时
proxy_send_timeout 60s;
# 接收响应超时
proxy_read_timeout 60s;
# 缓冲区大小
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
5.4 代理重试配置
nginx
location / {
proxy_pass http://backend;
# 失败时重试到其他服务器
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 30s;
}
5.5 HTTP/1.1和长连接
nginx
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
# 保持到上游的长连接
keepalive 32;
}
location / {
proxy_pass http://backend;
# 必须使用HTTP/1.1才能支持长连接
proxy_http_version 1.1;
# 清空Connection头,启用长连接
proxy_set_header Connection "";
}
长连接的效果对比:
没有长连接(HTTP/1.0):
客户端 ←→ Nginx ←→ 后端
每个请求都建立新TCP连接
三次握手 + 四次挥手
延迟增加 30-100ms
有长连接(HTTP/1.1):
客户端 ←→ Nginx ←→ 后端
连接复用,请求pipeline
延迟降低,吞吐提高
5.6 WebSocket代理
nginx
location /ws/ {
proxy_pass http://websocket_backend;
# WebSocket必须
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# WebSocket超时(要设置长一些)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
六、缓存配置:Nginx也是缓存服务器
6.1 为什么要用Nginx缓存
无缓存:
用户A请求 /api/data → Nginx → 后端(耗时500ms)
用户B请求 /api/data → Nginx → 后端(耗时500ms)
用户C请求 /api/data → Nginx → 后端(耗时500ms)
有缓存:
用户A请求 /api/data → Nginx缓存未命中 → 后端(500ms)→ 存入缓存
用户B请求 /api/data → Nginx缓存命中 → 直接返回(5ms)
用户C请求 /api/data → Nginx缓存命中 → 直接返回(5ms)
6.2 缓存配置
nginx
# 在http块定义缓存路径和参数
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=api_cache:100m
max_size=10g
inactive=60m
use_temp_path=off;
server {
listen 80;
location /api/ {
proxy_pass http://backend;
# 使用缓存
proxy_cache api_cache;
# 缓存key
proxy_cache_key "$scheme$request_method$host$request_uri";
# 缓存有效期
proxy_cache_valid 200 5m; # 200响应缓存5分钟
proxy_cache_valid 404 1m; # 404响应缓存1分钟
# 缓存状态头(X-Cache-Status: HIT/MISS/BYPASS)
add_header X-Cache-Status $upstream_cache_status;
# 缓存绕过(请求头带Cache-Control: no-cache时绕过缓存)
proxy_cache_bypass $http_cache_control;
# 不缓存的响应码
proxy_no_cache $http_pragma $http_authorization;
}
}
6.3 缓存清理
nginx
# 定义清理缓存的location
location ~ /purge(/.*) {
proxy_cache_purge api_cache $scheme$request_method$host$1;
return 200 "Cache purged\n";
}
# 使用方式:POST请求到 /purge/api/data
# curl -X POST http://example.com/purge/api/data
6.4 缓存问题排查
bash
# 查看缓存文件
ls -la /var/cache/nginx/
# 查看缓存大小
du -sh /var/cache/nginx/
# 清理所有缓存
rm -rf /var/cache/nginx/*
七、限流:保护后端服务的利器
7.1 连接数限流
nginx
# 基于IP的并发连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
# 每个IP最多10个并发连接
limit_conn conn_limit 10;
# 超过限制时的日志
limit_conn_log_level warn;
# 超过限制时返回的状态码
limit_conn_status 503;
}
7.2 请求速率限流
nginx
# 基于IP的请求速率限制
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
server {
# 每秒最多10个请求
# burst=20表示允许突发20个请求
# nodelay表示突发请求立即处理,不延迟
limit_req zone=req_limit burst=20 nodelay;
limit_req_log_level warn;
limit_req_status 429;
}
7.3 带宽限制
nginx
server {
# 限制每个连接的最大带宽为1MB/s
limit_rate 1m;
# 下载超过10MB后限速到512KB/s
limit_rate_after 10m;
}
7.4 多维度限流
nginx
# 按IP限流
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
# 按server限流(所有IP共享)
limit_req_zone $server_name zone=server_limit:10m rate=1000r/s;
# 按token限流(需要配合JWT等)
limit_req_zone $http_authorization zone=user_limit:10m rate=10r/s;
server {
location /api/ {
# 同时应用多个限流规则
limit_req zone=ip_limit burst=20 nodelay;
limit_req zone=server_limit burst=100;
proxy_pass http://backend;
}
}
八、HTTPS配置:安全是底线
8.1 基础HTTPS配置
nginx
server {
listen 443 ssl http2;
server_name example.com;
# 证书
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# 安全协议版本
ssl_protocols TLSv1.2 TLSv1.3;
# 安全加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# Session缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
}
8.2 免费证书Let's Encrypt
bash
# 使用certbot申请证书
apt install certbot python3-certbot-nginx
# 自动配置Nginx
certbot --nginx -d example.com -d www.example.com
# 续期
certbot renew
8.3 HTTP跳转HTTPS
nginx
# 方法1:301重定向
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# 方法2:使用if(不推荐)
server {
listen 80;
server_name example.com;
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
# 方法3:HSTS(强制HTTPS)
server {
listen 443 ssl;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
8.4 TLS版本检测与加固
bash
# 测试TLS配置
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# 查看支持的协议和密码套件
nmap --script ssl-enum-ciphers example.com
九、生产环境配置示例
9.1 完整的反向代理配置
nginx
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 基础优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 10000;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# 限流
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
# 代理配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:100m max_size=10g inactive=60m;
upstream backend {
server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 backup;
keepalive 32;
}
server {
listen 80;
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
# API接口
location /api/ {
limit_req zone=api_limit burst=200 nodelay;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_cache api_cache;
proxy_cache_valid 200 5m;
add_header X-Cache-Status $upstream_cache_status;
}
# 静态资源
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
root /var/www/static;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# 默认
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
十、踩坑实录:这些年我踩过的Nginx坑
坑1:proxy_set_header Host配置错误导致后端取不到正确的Host
某次排查问题,发现后端应用获取到的Host总是"localhost",而不是配置的域名。
原因: proxy_set_header Host默认是host,但如果后端有虚拟主机配置,可能需要传host,但如果后端有虚拟主机配置,可能需要传host,但如果后端有虚拟主机配置,可能需要传host:$proxy_port。
解决: 显式配置
proxy_set_header Host $host;
坑2:正则location的优先级导致配置不生效
配置了一个正则location来处理/api/开头的请求,但发现不生效。
原因: 可能有更早的正则location匹配了,或者普通location先匹配了。
解决: 理解Nginx location优先级,或使用^~前缀匹配。
坑3:upstream配置顺序影响负载均衡
两台服务器配置完全相同,但一台负载总是比另一台高很多。
原因: IP Hash时,某些IP段恰好被哈希到同一台服务器。
解决: 切换到加权轮询或最小连接算法。
坑4:reload导致请求失败
执行nginx -s reload后,有几秒请求失败。
原因: 新旧worker进程切换时,accept锁的争抢导致短暂不可用。
解决: 使用nginx -s reload而不是kill -HUP,或使用优雅升级nginx -s reopen。
坑5:长连接没有生效
配置了keepalive,但发现性能没有提升。
原因: 忘记设置
proxy_http_version 1.1和proxy_set_header Connection ""。解决: 这两个配置缺一不可。
十一、总结
Nginx是现代互联网架构的核心组件:
- 反向代理:隐藏后端,提供统一的入口
- 负载均衡:多种策略,灵活调度
- 静态资源服务:高性能文件服务
- 缓存:减轻后端压力
- 限流:保护后端服务
- HTTPS:安全加密通信
最佳实践:
- 合理配置worker进程数和连接数
- 正确传递代理头信息
- 使用长连接减少连接开销
- 配置限流保护后端
- 启用缓存提升性能
- HTTPS是标配,TLS 1.2起步
血的教训:
每次修改Nginx配置前,先用
nginx -t语法检查,再nginx -s reload热加载。永远不要在没有检查的情况下直接重启。
思考题: 你们公司的Nginx目前用的哪种负载均衡策略?有没有遇到过头疼的Nginx问题?你的Nginx配置文件有多长?有没有可以优化的地方?
个人观点,仅供参考