核心使用配置
这一篇是日常配置的主菜:静态站点怎么托管(root 和 alias 到底差在哪)、反向代理怎么配得又稳又快、upstream 的四类负载策略怎么选、HTTPS 怎么上一套能过安全扫描的配置,最后是 gzip、缓存和重定向。所有片段都可以直接拼进 conf.d 下的站点文件,改完记得 nginx -t && nginx -s reload。
静态站点
root 与 alias 的区别
两者都指向磁盘路径,但拼接方式完全不同:
| 指令 | 拼接规则 | location /static/ 请求 /static/logo.png 时 |
|---|---|---|
root /var/www; | root + 完整 URI | /var/www/static/logo.png |
alias /var/www/assets/; | 用 alias 替换掉 location 匹配的部分 | /var/www/assets/logo.png |
# root:目录要跟着 URI 结构走
location /static/ {
root /var/www;
}
# alias:目录可以随便放,注意 alias 结尾的斜杠
location /assets/ {
alias /var/www/assets/;
}
第一,alias 结尾的斜杠必须和 location 前缀的斜杠配对。location /assets/ 配 alias /var/www/assets;(少了斜杠)会拼出 /var/www/assetslogo.png。第二,alias 和 try_files 一起用有历史 bug($uri 里包含 location 前缀),要么避开这个组合,要么升级到较新版本。能用 root 就别用 alias。
try_files:按顺序找文件
try_files 是静态站点的灵魂,语法是「依次尝试这些路径,最后一个参数是兜底」:
server {
listen 80;
server_name static.local;
root /var/www/static;
# 先找文件,再找目录下的 index,都没有就 404
location / {
try_files $uri $uri/ =404;
}
}
=404 是返回码兜底,也可以写成 @named 走命名 location,或者直接写一个 URI 做内部跳转。
SPA 的 history 路由回退
Vue/React 的 history 模式刷新 /user/123 时,服务器上并没有这个文件,必须把「找不到」的请求交回 index.html,由前端路由接管:
server {
listen 80;
server_name spa.local;
root /var/www/spa/dist;
location / {
try_files $uri $uri/ /index.html;
}
# index.html 绝不能长缓存,否则发版后用户一直看到旧页面
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1;
}
# 带 hash 的静态资源可以放心长缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
SPA 的 try_files ... /index.html 一旦写成全局 location /,/api/* 的请求也会被喂给前端,后端接口全变 200 空页面。正确做法是给 API 单独写一个 location /api/ 做 proxy_pass,前缀匹配优先于 /。
反向代理进阶
完整的请求头集合
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# 透传请求 ID,便于跨服务串日志
proxy_set_header X-Request-ID $request_id;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
超时三件套
| 指令 | 含义 | 默认值 | 调小的场景 |
|---|---|---|---|
proxy_connect_timeout | 与上游建立 TCP 连接的超时 | 60s | 内网后端,5s 足够,挂了要快速失败 |
proxy_send_timeout | 向上游发送请求的超时(两次写之间) | 60s | 上传大文件时反而要调大 |
proxy_read_timeout | 等待上游返回响应的超时(两次读之间) | 60s | 普通接口 30-60s;长轮询/SSE 调大或关缓冲 |
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 上游不可用时重试:只对幂等请求安全,POST 要谨慎
proxy_next_upstream error timeout http_502 http_503;
proxy_read_timeout 超时的直接后果就是 504。调它的原则是「比后端最慢的正常接口再宽一点」,不要为了消 504 一味调到 600s——那样只会把故障从 504 变成用户干等。
buffering:缓冲开关
默认 proxy_buffering on,Nginx 会先把上游响应读进缓冲区(内存写满转临时文件),再慢慢发给客户端。好处是慢客户端不占用后端连接,坏处是流式响应会被攒批。
# 默认行为,适合绝大多数 API 和页面
proxy_buffering on;
proxy_buffer_size 4k; # 响应头缓冲区
proxy_buffers 8 16k; # 响应体缓冲区
proxy_busy_buffers_size 32k;
# 大文件下载、SSE、WebSocket、AI 流式输出:关掉缓冲
location /api/stream/ {
proxy_pass http://app_backend;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
chunked_transfer_encoding on;
}
判断标准很简单:后端返回的内容需要「边生成边推给用户」就关缓冲,否则保持默认。
负载均衡
四种上游策略
# 1. 轮询(默认):请求依次分发,最简单
upstream backend_rr {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
# 2. 加权轮询:按机器配置分配流量
upstream backend_weight {
server 10.0.0.11:8080 weight=5 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 weight=1;
server 10.0.0.13:8080 backup;
}
# 3. ip_hash:同一客户端 IP 固定到同一台,适合有本地 session 的老应用
upstream backend_ip {
ip_hash;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
# 4. least_conn:优先发给当前连接数最少的节点,长连接场景更均衡
upstream backend_least {
least_conn;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
选型建议:
- 应用无状态 → 轮询或加权轮询,最省事。
- 应用有本地 session →
ip_hash应急,但更好的做法是把 session 外置到 Redis,然后回到轮询(ip_hash在 NAT 后面会把一堆用户压到同一台)。 - 请求耗时差异大,或需要按 URL 稳定分流 →
least_conn/hash $request_uri consistent;,前者均衡连接,后者保证同一资源固定落到同一台。
健康检查:开源版靠被动
开源版 Nginx 没有主动健康检查,靠的是被动摘除:
max_fails=3:在fail_timeout窗口内失败 3 次,就把这台标记为不可用。fail_timeout=10s:标记不可用的持续时间,10 秒后再试探。
这些参数写在 upstream 的 server 行上。失败包括连接拒绝、超时、以及 proxy_next_upstream 里列出的状态码。
health_check interval=5s; 这类主动探测属于 NGINX Plus。开源版想要主动探测,思路是用外部脚本(或 keepalived 的 vrrp_script)定时 curl 健康接口,失败就改配置 reload,或者干脆用 K8s 的 readinessProbe 兜住。规模大一点的场景直接用 SLB/云 LB,把健康检查交给它。
upstream 长连接
默认情况下 Nginx 到上游是短连接,每个请求都三次握手,高 QPS 下 TIME_WAIT 会堆成山。开启连接池:
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 64; # 每个 worker 维持的空闲长连接数
keepalive_requests 1000; # 单连接最多复用多少次请求
keepalive_timeout 60s;
}
server {
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1; # 必须 1.1
proxy_set_header Connection ""; # 必须清空 Connection 头
}
}
三行缺一不可。少了 proxy_http_version 1.1 或 Connection "",连接池等于白配。
HTTPS
证书配置
server {
listen 443 ssl http2; # 1.25.1+ 改为 listen 443 ssl; 再加 http2 on;
server_name example.com;
ssl_certificate /etc/nginx/certs/example.com.pem; # 证书链(含中间证书)
ssl_certificate_key /etc/nginx/certs/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off; # TLS1.3 下交给客户端选更合适
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP 装订,减少客户端验链耗时
ssl_stapling on;
ssl_stapling_verify on;
resolver 223.5.5.5 8.8.8.8 valid=300s;
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
几个坑:
- 证书文件要包含完整链(服务器证书 + 中间证书),只配叶子证书时,部分安卓和老浏览器会报证书不可信。
ssl_certificate路径权限:worker 以www-data跑,证书目录至少要让它可以读。- HSTS 想清楚再开:一旦下发
max-age,浏览器在有效期内只走 HTTPS,回退很麻烦。先小max-age试水。 resolver必须配才能用ssl_stapling(证书目录里放自签证书时浏览器会告警,但链路能通,可用来验证配置)。
HTTP 跳 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
return 301 比 rewrite 高效,而且不会触发 location 重匹配。
gzip 与缓存
gzip 压缩
gzip on;
gzip_vary on; # 加 Vary: Accept-Encoding,CDN 场景必须
gzip_comp_level 5; # 1-9,5 是性价比拐点,9 反而更耗 CPU
gzip_min_length 1k; # 太小的文件压了不划算
gzip_proxied any; # 代理请求也压
gzip_types text/plain text/css text/xml text/javascript
application/javascript application/json application/xml
application/rss+xml image/svg+xml;
两个注意点:text/html 不用写(默认就会压);图片、视频不要压,本身就是压缩格式,再压只浪费 CPU。已经有 .gz 预压缩文件的,可以上 gzip_static on; 直接发预压好的文件,省掉实时压缩开销。
expires 与缓存头
# 带 hash 的资源:一年,immutable 告诉浏览器不用再验证
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 入口:不缓存,保证发版即时生效
location = /index.html {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# 接口:不缓存
location /api/ {
add_header Cache-Control "no-store";
}
proxy_cache 基础
Nginx 把上游响应缓存到本地磁盘,扛热点查询、防止后端被打穿:
# 定义在 http 层
proxy_cache_path /var/cache/nginx/data levels=1:2
keys_zone=api_cache:100m max_size=10g
inactive=60m use_temp_path=off;
server {
location /api/public/ {
proxy_pass http://app_backend;
proxy_cache api_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on; # 同一 key 只放一个请求回源
# 调试必备:响应头里能看到 HIT / MISS / EXPIRED
add_header X-Cache-Status $upstream_cache_status always;
}
}
proxy_cache_use_stale 是关键的一行:上游挂了还能拿旧缓存顶着,对「能容忍短暂数据陈旧」的接口非常有用。清理缓存直接删目录里的文件再 reload,别指望有 purge 指令(那是商业版)。
rewrite 与 return
# 1. 整站跳转:return 最直接
server {
listen 80;
server_name old.example.com;
return 301 https://new.example.com$request_uri;
}
# 2. 单路径永久跳转
location = /old-page {
return 301 /new-page;
}
# 3. rewrite:改写后继续走 location 匹配(last)或就地停住(break)
rewrite ^/user/(.*)$ /profile?id=$1 last;
rewrite 的四个 flag:
| flag | 行为 | 用在哪 |
|---|---|---|
last | 改写后重新匹配 location | 绝大多数情况 |
break | 改写后在当前位置继续处理,不再匹配 | 配合 proxy_pass 改写上游路径 |
redirect | 返回 302 | 临时跳转 |
permanent | 返回 301 | 永久跳转,SEO 友好 |
rewrite 加 last 会重新匹配 location,如果规则之间互相指来指去,Nginx 会在 10 次内部跳转后放弃,日志里报 rewrite or internal redirection cycle。典型触发是「去掉结尾斜杠」的规则写反了方向。排查方法见第 6 篇。
小结
- 静态站点:
root拼完整 URI、alias替换匹配前缀,能root不alias;SPA 用try_files $uri $uri/ /index.html,但 API 路径要单独location挡住。 - 反向代理:四个必备头 + 超时三件套 + 按需关 buffering;504 是
proxy_read_timeout,502 是连不上或上游主动断开。 - 负载均衡:无状态轮询/加权,有 session 先外置再轮询,耗时差异大用
least_conn;长连接三件套(keepalive、proxy_http_version 1.1、Connection "")缺一不可。 - HTTPS:证书链要完整、协议只留 TLSv1.2/1.3、HTTP 用
return 301跳转,HSTS 谨慎开。 - 缓存分两层:浏览器侧靠
expires+Cache-Control,服务端靠proxy_cache,配X-Cache-Status验证命中。
配置会写只是及格线。下一篇解决「怎么把它稳稳地跑在生产上」:部署清单、多实例 + keepalived 高可用、配置版本管理和平滑升级。