Nginx 大概是运维世界里「用得多、讲得少」的典型:装完能用,但遇到路径改写、WebSocket 断连、502 排查时就抓瞎。这篇把最常用的配置场景拆开讲清楚——每个片段都可以直接抄,但更重要的是理解它为什么这么写。
为什么是 Nginx
- 事件驱动 + 异步非阻塞:几万并发连接只用几个 worker 进程,内存占用稳定;
- 内置能力全:反向代理、负载均衡、缓存、限流、gzip、TLS 都开箱即用;
- 配置即文档:一个 server 块描述一个站点,比代码里的路由更容易审阅。
文件结构与配置层次
# Ubuntu / Debian 系的典型结构
/etc/nginx/
├── nginx.conf # 主配置(worker 数、日志、gzip 等全局项)
├── conf.d/ # 站点配置(*.conf 自动 include)
├── sites-available/ # 可选:站点仓库
└── sites-enabled/ # 可选:软链生效的站点
# 常用管理命令
nginx -t # 校验配置语法(改完必须先跑这个)
systemctl reload nginx # 平滑重载(不中断连接)
systemctl status nginx
server 与 location:请求怎么被路由
一个请求进入 Nginx,先按 listen + server_name 找到 server 块,再按 location 的匹配规则决定处理方式。location 的优先级是面试高频,也是配置事故高发区:
# 匹配优先级(从高到低):
# 1. = 精确匹配:location = /health
# 2. ^~ 前缀匹配且不再尝试正则:location ^~ /static/
# 3. ~ 正则匹配(按出现顺序,第一个命中生效);~* 不区分大小写
# 4. 普通前缀匹配(最长前缀生效)
# 5. / 兜底
server {
listen 80;
server_name example.com www.example.com;
location = /health {
return 200 "ok\n";
add_header Content-Type text/plain;
}
location ^~ /static/ {
root /var/www/site; # /static/a.css → /var/www/site/static/a.css
expires 7d;
}
location ~* \.(png|jpg|webp|svg)$ {
root /var/www/site;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}
反向代理核心:proxy_pass 与转发头
最容易踩的坑是 proxy_pass 结尾有没有斜杠:带路径会替换匹配部分,不带路径会原样拼接。这条规则不理解,路径重写就会诡异丢前缀。
location /api/ {
# http://host/api/users → http://127.0.0.1:8080/users (吃掉 /api 前缀)
proxy_pass http://127.0.0.1:8080/;
# http://host/api/users → http://127.0.0.1:8080/api/users (保留原样)
# proxy_pass http://127.0.0.1:8080;
}
# 一套通用的转发头(后端取真实信息全靠它们)
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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 5s; # 连后端超时
proxy_read_timeout 60s; # 读后端超时(长任务要调大)
proxy_send_timeout 60s;
}
后端做限流、审计、直链生成时,X-Real-IP / X-Forwarded-For 就是唯一可信来源;只在内网信任链上使用,外部进来的伪造头要在边缘层清掉。
WebSocket 升级:两个头缺一不可
WebSocket 断连最常见的根因就是 Nginx 没配升级头。Connection 和 Upgrade 必须成对出现:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 300s; # 心跳间隔要小于这个值,否则会被静默断开
}
静态资源与传输优化
# nginx.conf 全局层开启 gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types text/css application/javascript application/json image/svg+xml;
# 带 hash 的构建产物:长缓存 + immutable,靠文件指纹替换
location ~* \.[0-9a-f]{8,}\.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# sendfile 零拷贝(大文件/静态站收益明显)
sendfile on;
tcp_nopush on;
缓存策略一句话版:HTML 不缓存(协商缓存即可),带指纹的 JS/CSS/图片长缓存。发布新版本的唯一动作是把 HTML 里的引用换掉。
限流:单机版的「闸门」
# http 层定义限流区:按 IP 每秒 10 个请求,突发 20 个
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429; # 超限返回 429 而不是默认的 503
proxy_pass http://127.0.0.1:8080;
}
注意限流是单机维度的:多台机器各自限流等于总阈值 × 台数。真要做全局配额,得把计数放到 Redis 那类共享存储里。
HTTPS 与 HTTP/2
# 用 certbot 签证书并自动续期
apt install certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com
systemctl list-timers | grep certbot # 确认自动续期定时器在跑
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
# 80 端口统一跳 443(Let's Encrypt 校验路径除外)
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
}
502 / 504 排查清单
nginx -t和systemctl status:先确认配置生效、进程健康;- 看
/var/log/nginx/error.log的具体 errno:Connection refused(后端没起/端口错)、upstream timed out(处理太慢)、no live upstreams(都被判死); curl -v http://127.0.0.1:3000/:绕过 Nginx 直连后端,确认锅在谁头上;- SELinux 没放行
httpd_can_network_connect时,Nginx 连本机后端也会被拒; - 504 大多是真慢:调大
proxy_read_timeout只是缓解,根本方案是把慢接口改成异步任务; - 日志里加
$upstream_response_time $request_time,用数据分清「后端慢」还是「网络慢」。
Nginx 的配置没有魔法:理解匹配优先级和代理语义这两个模型,剩下的都是可查的指令拼装。
总结
本文覆盖了 Nginx 日常运维的完整拼图:server/location 路由 → 反向代理与转发头 → WebSocket 升级 → 缓存与 gzip → 限流 → HTTPS/HTTP2 → 故障排查。建议把自己的站点配置对照文中的片段过一遍,尤其是 proxy_pass 斜杠、升级头、超时这三处——线上 90% 的「玄学问题」都出在这里。