跳到主要内容

核心使用配置

这一篇是日常配置的主菜:静态站点怎么托管(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 的两个坑

第一,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";
}
}
别把 API 也回退成 index.html

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 死循环

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 高可用、配置版本管理和平滑升级。