← 返回博客
语言: English 中文
Dev 2026-05-23 11 分钟

Nginx location 与 proxy_pass:最容易破坏部署的斜杠规则

Nginx 是俄罗斯工程师 Igor Sysoev 厌倦了 Apache 每连接派生一个进程之后,用事件驱动 I/O 从头重写的产物。首个公开版本 2004 年 10 月。今天 nginx 在前 100 万网站里承担的份额比任何其他服务器都大,作为 Kubernetes ingress 的反向代理在前线工作,并且是 OpenResty、Tengine、Cloudflare 自家边缘背后的底料。配置语言 2002 年被设计为“易读”——2026 年它最知名的反而是那一小撮没被修过的、专门坑住运维的歧义。

NginxC10K反向代理TLSHTTP/2缓存

Nginx 第一次写正确出了名地难,因为它的配置语言表面上像一个普通的 key value; 配置,语义却是个小型声明式 DSL,规则你被咬过才知道。本文是这些咬痕的导览。

架构,简版

Apache 的传统模型:每连接一进程(或一线程)。请求来,分配一个 worker 处理它的整个生命周期,包括所有阻塞 I/O。一万并发就是一万 worker,内核调度成瓶颈。

Nginx 的模型:少量 worker 进程(通常一核一个),每个跑一个事件循环。每个 worker 用 epoll(Linux)/ kqueue(BSD)/ IOCP(Windows)多路复用,并发处理数千连接。当某连接没事做(等网络、等磁盘)时,worker 切到另一个连接而不是阻塞。

实际后果:同样的 RAM,nginx 比 Apache 撑得起多得多的连接,单核机器只跑静态文件就能打满千兆带宽。代价是所有阻塞工作要么卸到 worker 池aio threads),要么推到上游。误配的 auth_request 打到一个慢后端会饿死整个 worker,让 nginx 表现得比应有的差。

配置文件结构

nginx 配置是层次的。顶层("main" 上下文)配置 worker 与全局设置。events 上下文配事件循环。http 里定义 server 块(每个虚拟主机一个),每个 serverlocation 块(每个 URL 前缀或模式一个)。

events { ... }
http {
  upstream api { ... }
  server {
    listen 443 ssl;
    server_name example.com;
    location / { ... }
    location /api/ { proxy_pass http://api/; }
  }
}

指令从外向内继承,除非被覆盖。有些指令只能在某些上下文里用。把指令放错上下文时报错统一糟糕("unknown directive");只有文档是哪条指令支持哪些上下文的权威。

location 匹配:所有人都被坑过的规则

这是 nginx 最容易被误解的部分,也是大多数生产配置在悄悄出错的地方。

一个 server 块可以有多个 location 块。请求来时 nginx 挑选恰好一个来处理,按以下顺序:

  1. 精确匹配= 修饰符)立即胜出。
    location = /healthz { ... }
    
  2. 前缀匹配^~ 修饰符)—— 如果最长这种前缀匹配,它胜出(跳过正则匹配)。
    location ^~ /static/ { ... }
    
  3. 正则匹配~ 大小写敏感、~* 不敏感)。第一个匹配的正则胜出,按它们在配置里出现的顺序。
    location ~* \.(jpg|png|gif)$ { ... }
    
  4. 普通前缀匹配(无修饰符)。如果没正则匹配,最长普通前缀胜出。

三种失败模式:

  • 两个前缀 location,最长那个赢。 location /api/v1/users 击败 location /api/v1/。加新路由时容易忘。
  • 正则顺序重要。 第一个匹配赢,不是最具体那个。重排正则块会改变行为。保持纪律:"正则块放底部,按优先级排"。
  • ^~ 完全跳过正则。location ^~ /static/location ~ \.html$ 时,请求 /static/page.html 会去静态处理,不去 HTML 处理。常常是想要的;偶尔出乎意料。

诊断"哪个 location 处理了我的请求?"的命令:在每个候选里加 add_header X-Loc "matched-here" always;,发请求看响应头。省你几小时

proxy_pass 与那个能毁了你一天的尾斜杠

location /api/ {
  proxy_pass http://backend/;     # 一个斜杠
}
location /api/ {
  proxy_pass http://backend;      # 没斜杠
}

它们不一样。请求 /api/users

  • http://backend/(带尾斜杠):nginx 把匹配的前缀 /api/ 去掉,转发 /users 给后端。
  • http://backend(无尾斜杠):nginx 把完整原始 URI /api/users 转发给后端。

有人为这一条调试半天。几乎总是想要的"挂载路径"模式

location /api/ {
  proxy_pass http://backend/;     # 双尾斜杠 → 路径重写
}

如果你需要逐字保留路径(透明反向代理):

location /api/ {
  proxy_pass http://backend;      # 无尾斜杠 → 透传
}

正则 location 不能用重写形式——location 是正则时,带 URI 的 proxy_pass 会被拒。这种情况用 rewrite 显式构造上游路径。

alias vs root:选对那个

两条指令都把 URL 映射到文件系统路径,行为不同

location /static/ {
  root /var/www;
}
# 请求 /static/style.css → /var/www/static/style.css

location /static/ {
  alias /var/www/assets/;
}
# 请求 /static/style.css → /var/www/assets/style.css

root 追加 URL 到配置路径。alias 替换匹配的前缀为配置路径。

陷阱:alias 忘了尾斜杠。没尾斜杠路径会怪异地拼接:

location /static/ {
  alias /var/www/assets;     # 无尾斜杠
}
# 请求 /static/style.css → /var/www/assetsstyle.css ← 坏了

规则:alias 用于目录形式 location 时,location 以 / 结尾、alias 路径也以 / 结尾。不匹配就是 bug。

更宽泛的规则:优先 root,除非确实需要路径替换才用 aliasroot 意外更少。

try_files:能找到静态就服务,否则降级

SPA 常见模式:

location / {
  try_files $uri $uri/ /index.html;
}

try_files 按顺序检查每个参数,服务第一个存在的。最后一个参数是内部的——作为 fallback URI 使用,不会重新跑 location 匹配,避免循环。

Phoenix 风格的 "/api/* 走后端、其他都是静态 SPA" 应用:

server {
  root /var/www/spa;

  location /api/ {
    proxy_pass http://api/;
  }

  location / {
    try_files $uri $uri/ /index.html;
  }
}

这是"静态前端 + 反向代理 API"的标准配置。前缀匹配的 location 块顺序无关,nginx 选最长的。

Forwarded headers:必须设的那些

nginx 反向代理给后端时,后端默认看到的是 nginx 的 IP,不是客户端的。不干预的话:

  • 后端日志里所有请求来自 127.0.0.1
  • Geo-IP 失效。
  • 后端的限流失效。

标准反向代理头集合:

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;

后端需要信任这些头。信任必须以"请求来自已知代理"为前提——否则你网络外的攻击者发 X-Forwarded-For: 127.0.0.1 就被当 localhost。

更新的 Forwarded 头(RFC 7239)是规范认可的替代:Forwarded: for=192.0.2.43;proto=https;host=example.com。定义更清楚但支持没那么普遍,许多后端仍期望 X-Forwarded-*

WebSocket:Upgrade 之舞

朴素 proxy_pass 配置会丢掉 WebSocket upgrade。代理 WebSocket:

location /ws/ {
  proxy_pass http://backend/;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_read_timeout 3600s;
}

proxy_http_version 1.1 是必需的——HTTP/1.0 没有 upgrade 机制。Connection: upgrade 头让 nginx 不要发它默认的 Connection: close

长连 WebSocket 还需要把 proxy_read_timeout 调高,否则 nginx 默认 60 秒就把空闲连接杀掉。

TLS:真正重要的配置

最低的现代配置(假设有 Let's Encrypt 等):

listen 443 ssl;
listen [::]:443 ssl;
http2 on;

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_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

注意:

  • 关掉 TLS 1.0 和 1.1。 都有已知问题;2020 年主流浏览器全部弃用。现代 nginx 默认 1.2+,确认一下。
  • http2 on 替换旧的 listen 443 ssl http2 写法(nginx 1.25 起弃用)。如果能跑 HTTP/3,加 listen 443 quic reuseportadd_header Alt-Svc 'h3=":443"; ma=86400';
  • HSTS 只用在生产。HSTS 钉死到坏证书的预发环境是数天恢复。
  • ssl_prefer_server_ciphers off 是现代推荐——让客户端从服务器允许列表里挑。"反过来"在 BEAST/CRIME 时代是对的,今天不对。
  • cipher 列表: Mozilla 的 SSL Config Generator 给当前最佳实践 cipher 字符串。每隔几年更新一次。

HTTP→HTTPS 跳转:

server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  return 301 https://$server_name$request_uri;
}

return 301,不是 rewriterewrite 更贵,是用来改路径的,不是做整 URL 跳转。

压缩:gzip 与 brotli

gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_vary on;
  • gzip_comp_level 6 是实践甜蜜点。再高 CPU 不划算。
  • gzip_types 指定要压缩的 content-type。text/html 默认开,列表里不写。新字体格式、现代图片格式没在列表里就不压缩出
  • gzip_vary onVary: Accept-Encoding,告诉缓存响应随编码变化。
  • 不要压已经压缩过的 资产(jpg、png、mp4、woff2)。已压缩数据再压是负收益。
  • Brotlingx_brotli 模块)在 HTML/CSS/JS 上比 gzip 多压 15–25%,速度相当。如果构建里包含就值得用——多数现代包默认不带。

限流:双桶系统

nginx 有两种限流机制:

  • limit_req —— 基于漏桶的请求速率限流。按 key(通常客户端 IP)限 RPS。
  • limit_conn —— 并发连接限流。按 key 限同时连接数。
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;

server {
  location /api/ {
    limit_req zone=api burst=20 nodelay;
    limit_conn conn 10;
    proxy_pass http://backend/;
  }
}

陷阱:在 Cloudflare 这类 CDN 后面,$remote_addr 是 CDN 的 IP,不是客户端的,所有用户都被合到同一个 key 里。用 $http_cf_connecting_ip(Cloudflare 专用),或设 set_real_ip_fromreal_ip_header X-Forwarded-For

常见坑

  • location 匹配修饰符选错。add_header 显式调试。
  • proxy_pass URI 尾斜杠。 两份看起来一样的配置行为不同。
  • alias 尾斜杠。 与 location 尾斜杠匹配。
  • 代理时丢 header。 显式 proxy_set_header Host $host;
  • HTTPS 跳转用 rewrite 而不是 return
  • server_name 与请求 Host 不匹配 会落到默认 server 块。证书错配常表现为"显示的是别的网站"。
  • if 滥用。 nginx 的 iflocation 块里出名地诡异;官方 wiki 那页标题就是 "If is Evil"。能用 try_filesmaperror_page 就用。
  • 忘了 client_max_body_size 默认 1 MB。文件上传超就是 413 Payload Too Large。
  • 默认 keepalive_timeout 75s 在高负载下保留太多空闲连接。按流量调。
  • 不检查语法就 reload 配置。 永远先 nginx -t。坏的 reload 杀掉运行中的服务器。
  • 改完配置忘了 nginx -s reload 运行中进程还在用老配置。
  • access_log off 关掉了你事故时想要的日志。用一种简化日志格式而非禁日志

运维卫生

  • 每次配置改动用 nginx -t 校验,再 reload。
  • reload(不是 restart)是优雅的:nginx -s reload
  • 日志格式包含 $request_time$upstream_response_time$status 用于性能调优。
  • logrotate + nginx -s reopen(或 USR1 信号)轮转日志,否则文件会涨爆。
  • 如果你需要 TLS 1.3 0-RTT、高级 HTTP/3、主动健康检查,把 nginx 放在别的东西后面——Caddy 与 HAProxy 各自把某些事做得更好。多数负载里 nginx 仍是对的默认

用 nginx 还是别的

  • nginx 用于:TLS 终止、反向代理、静态文件、通用 HTTP 路由。默认选择;稳定、快、文档全。
  • Caddy 用于:低配置成本部署。自动 Let's Encrypt、现代 HTTP/3、配置语法更简洁。
  • HAProxy 用于:TCP 层负载均衡、高级健康检查、金融级可用。L4/L7 LB 的可观测性比 nginx 强。
  • Envoy 用于:service mesh 边车、原生 gRPC 路由、由控制面(Istio、Consul Connect)下发动态配置。
  • Traefik 用于:容器化环境,proxy 直接读 Docker / Kubernetes label。

nginx 的配置语言显出了岁月。Caddy 这种现代替代品做同样的事行数减半。但 nginx 装机量巨大、文档详尽、上面那些反直觉行为是稳定的反直觉——你知道了它们不会再变。

2026 年写新 nginx 配置,能省时间的纪律:用一个生成器(本站工具,或一份已知好用的模板),逐步定制,每次改动 nginx -t 后再 reload。这些坑不会被修,唯一的前进路径是知道它们在哪儿

主要参考资料

用于核对本文技术细节的标准与官方文档。

在线生成干净的 nginx 配置

本站 nginx 工具按你的勾选——TLS、反向代理、gzip、安全头、限流、缓存——生成一份能跑的 server 块,关键的坑(HTTP→HTTPS 跳转、WebSocket upgrade、proxy header)都接对。新站起步或老配置审计时很有用。本地生成,不离开浏览器。

打开 nginx 工具

相关文章

继续阅读同一主题领域的实践指南。

查看全部文章

Cookie 同意

我们使用 Cookie 来增强您的体验并展示相关广告。您可以自定义您的偏好。