Nginx 配置说明
这篇文章解决什么问题
很多人会用 Nginx,但真正出问题时,常常不是不会写一段能跑的配置,而是不清楚这些问题:
- 请求到底是怎么命中某个
server和location的 root、alias、index、try_files到底是什么关系- 为什么
proxy_pass多一个斜杠,后端收到的路径就变了 - 为什么
add_header在子层里写了一次,上层头部就没了 - 为什么代理链后面拿到的客户端 IP 不对
- 为什么看起来只是一个反向代理,结果要同时处理超时、缓存、Cookie、重定向、压缩和日志
这篇文章不只讲“怎么跑起来”,而是把日常最常用、也最容易配错的配置点放到同一张图里。
先说结论
- Nginx 配置最重要的不是单个指令,而是层级和匹配顺序。先把
http、server、location、upstream、map的关系理清,再写配置才不容易乱。 - 大多数线上问题,不是出在“不会写”,而是出在五个地方:
location选错、root/alias混用、proxy_pass路径替换搞错、转发头不完整、代理链里的真实 IP 没处理好。 - 对静态文件,优先想清楚
root、index、try_files。对反向代理,优先想清楚四件事:路径怎么转、头怎么传、超时怎么设、缓冲怎么开。 - 能用
return的重定向,不要先上rewrite。能用map的条件分流,不要把复杂判断堆进if。 - HTTP 代理和 TCP/UDP 代理不是一套配置。前者主要在
http下,后者主要在stream下。
先把配置结构看全
Nginx 配置不是平铺的,它是分层的。最常见的结构是这样:
nginx
worker_processes auto;
events {
worker_connections 1024;
}
http {
upstream backend {
server 127.0.0.1:3000;
}
map $http_host $is_admin {
default 0;
admin.example.com 1;
}
server {
listen 80;
server_name example.com;
location / {
}
}
}
stream {
server {
listen 3306;
}
}可以把它理解成这样:
| 层级 | 作用 |
|---|---|
main | 全局层,放进程级配置,比如 worker_processes |
events | 连接处理方式,比如 worker_connections |
http | HTTP 站点总容器,大部分 Web 配置都在这里 |
upstream | 上游服务组,给代理或负载分发用 |
map | 根据变量算出另一个变量,适合做条件映射 |
server | 一个虚拟站点,按端口和域名接请求 |
location | 站点内部按路径分流 |
stream | TCP/UDP 代理,不走 HTTP 语义 |
如果只记一条:
server决定“这个请求进哪个站”location决定“这个路径怎么处理”
请求是怎么命中配置的
先选 server
Nginx 先看请求到达的地址和端口,再结合 Host 头找对应的 server_name。
最常见的规则是:
- 先按
listen匹配地址和端口。 - 再按
server_name匹配域名。 - 如果没匹配到,就走这个端口上的默认站点。
- 如果没有显式
default_server,通常就是这个端口上的第一个server。
nginx
server {
listen 80;
server_name example.com www.example.com;
}
server {
listen 80 default_server;
server_name _;
return 444;
}这种“兜底站点”很有用,能避免未定义域名落到错误站点。
再选 location
location 的优先级不是“谁写前面谁先中”,而是按匹配规则来的。
| 写法 | 含义 | 优先级特点 |
|---|---|---|
location = / | 精确匹配 | 最高,命中就结束 |
location /path/ | 前缀匹配 | 先找最长前缀 |
location ^~ /images/ | 前缀匹配且禁止再测正则 | 前缀命中后不再看正则 |
location ~ \.php$ | 正则匹配,区分大小写 | 在前缀匹配后按出现顺序测试 |
| `location ~* .(jpg | png)$` | 正则匹配,不区分大小写 |
location @fallback | 命名位置 | 只用于内部跳转,不直接接普通请求 |
核心规则是:
- 先找最长前缀匹配
- 再按配置顺序测试正则
- 如果某个最长前缀带了
^~,就不再看正则 - 如果没有正则命中,就回到刚才记住的最长前缀
还有两个很容易忽略的点:
location匹配的是 URI 部分,不含查询参数- 匹配前会先对 URI 做规范化处理,比如解码
%XX、处理.和..
末尾斜杠会影响行为
如果一个前缀 location 以 / 结尾,并且里面是 proxy_pass、fastcgi_pass 这类转发,访问不带斜杠的同名路径时,Nginx 可能直接回 301,把它补成带斜杠的路径。
nginx
location /user/ {
proxy_pass http://user.example.com;
}这时访问 /user,很可能会被重定向到 /user/。
如果你不想要这个行为,要显式补一个精确匹配:
nginx
location = /user {
proxy_pass http://login.example.com;
}
location /user/ {
proxy_pass http://user.example.com;
}文件服务相关配置
root 和 alias 不是一回事
这是最常见的误区之一。
root
root 是把请求 URI 直接拼到目录后面。
nginx
location /i/ {
root /data/w3;
}请求 /i/top.gif 时,实际文件路径是:
text
/data/w3/i/top.gifalias
alias 是用一个目录去替换当前 location 对应的这段前缀。
nginx
location /i/ {
alias /data/w3/images/;
}请求 /i/top.gif 时,实际文件路径是:
text
/data/w3/images/top.gif所以判断很简单:
- 目录后面还要保留请求里的前缀,用
root - 目录就是这段前缀的替代品,用
alias
正则 location 里如果使用 alias,应该配合捕获组,不要直接硬拼。
nginx
location ~ ^/users/(.+\.(?:gif|jpe?g|png))$ {
alias /data/w3/images/$1;
}index 不是简单的“默认首页”
index 的作用是处理以 / 结尾的请求,比如 /docs/。
它会按顺序检查文件,并触发一次内部跳转。
nginx
location / {
index index.html index.php;
}这里容易误判的一点是:index 命中后,请求可能转成 /index.html,然后再被别的 location 接管。
nginx
location = / {
index index.html;
}
location / {
# 这里可能才是最终处理 /index.html 的地方
}所以首页“明明在 location = / 里配了,结果逻辑跑到别处去”,很多时候不是错觉,而是 index 的内部跳转在生效。
try_files 是静态站点和 SPA 的核心
try_files 会按顺序检查文件是否存在,找到就用,找不到就走最后一个后备目标。
普通静态站点
nginx
location / {
try_files $uri $uri/ =404;
}这表示:
- 先找当前路径对应的文件
- 再找目录
- 都没有就回
404
前端单页应用
nginx
location / {
try_files $uri $uri/ /index.html;
}这表示:
- 能命中真实静态文件就直接返回
- 否则回退到
index.html
这类写法适合 Vue、React 这种前端路由。
try_files 和命名位置
官方文档给出的等价关系很重要:
nginx
location / {
try_files $uri $uri/ @app;
}
location @app {
proxy_pass http://backend;
}本质上接近于:
nginx
location / {
error_page 404 = @app;
log_not_found off;
}所以它不是“语法糖”,而是一种很常用的静态优先,失败再转应用的控制方式。
autoindex 只适合明确要暴露目录列表的场景
nginx
location /downloads/ {
autoindex on;
autoindex_format html;
}它会在找不到 index 文件时输出目录列表。
这适合下载目录、内部文件浏览,不适合默认开在公共静态目录上。
error_page 不只是错误页美化
error_page 可以把错误码转到一个静态页,也可以转到命名位置做兜底处理。
nginx
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;或者:
nginx
location / {
error_page 404 = @fallback;
}
location @fallback {
proxy_pass http://backend;
}如果只是简单跳转或直接返回状态码,优先用 return;
如果要改 URI 并重新进入匹配流程,再考虑 rewrite。
反向代理配置
一定先看懂 proxy_pass
proxy_pass 最容易出错的地方,不是会不会写,而是URI 怎么传给上游。
1. proxy_pass 不带 URI
nginx
location /api/ {
proxy_pass http://backend;
}请求:
text
/api/users上游通常收到:
text
/api/users2. proxy_pass 带 URI
nginx
location /api/ {
proxy_pass http://backend/;
}同样请求:
text
/api/users上游通常收到:
text
/users因为匹配到的 /api/ 被替换成了 proxy_pass 里的 /。
3. 正则 location 里,不要随便给 proxy_pass 再拼 URI
官方文档明确提到:
如果 location 是正则,或者是命名位置,Nginx 无法可靠判断应该替换 URI 的哪一段,这时 proxy_pass 应该不带 URI。
nginx
location ~ ^/api/(.*)$ {
proxy_pass http://backend;
}4. rewrite break 后,proxy_pass 的 URI 处理也会变
nginx
location /name/ {
rewrite /name/([^/]+) /users?name=$1 break;
proxy_pass http://127.0.0.1;
}这时传给上游的是修改后的完整 URI。
所以只要配置里同时出现 rewrite 和 proxy_pass,就不要只盯着一行看,要把两者放一起读。
代理请求时,头部要主动想清楚
一个够用的反向代理模板通常至少要把这些头传对:
nginx
location / {
proxy_pass http://backend;
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;
}为什么这些头重要:
Host:很多应用按域名分租户、做回调、生成绝对 URLX-Real-IP:应用想看到客户端来源地址X-Forwarded-For:保留整条代理链X-Forwarded-Proto:告诉后端原请求是http还是https
还有一个容易忽略的事实:
如果你不显式写 proxy_set_header,Nginx 并不会把原始 Host 和 Connection 原样传给上游,而是会按自己的默认值处理。
WebSocket 需要额外配置
nginx
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}没有这两行:
UpgradeConnection "upgrade"
很多 WebSocket 服务会直接失败。
三种超时要分开看
| 指令 | 控制什么 |
|---|---|
proxy_connect_timeout | 和上游建立连接要等多久 |
proxy_send_timeout | 向上游发送请求时,两次写操作之间最多等多久 |
proxy_read_timeout | 从上游读取响应时,两次读操作之间最多等多久 |
很多人把 504 都理解成“后端慢”。
实际上有可能是:
- 连不上上游
- 请求发不出去
- 响应长时间没继续返回数据
所以超时要按阶段拆开看。
proxy_buffering 和 proxy_request_buffering
响应缓冲
nginx
proxy_buffering on;开启时:
- Nginx 会尽快把上游响应读进缓冲区
- 不够时还可能写临时文件
- 更适合普通页面、接口、缓存场景
关闭时:
- 响应边到边转发
- 更接近流式输出
- 上游慢,客户端也会更直接感受到
nginx
proxy_buffering off;请求体缓冲
nginx
proxy_request_buffering on;开启时:
- Nginx 先把整个请求体读完,再发给上游
关闭时:
- 请求体收到一部分,就往上游发一部分
- 适合流式上传,但一旦已经开始往上游发送,请求就不容易再切到下一个上游
nginx
proxy_request_buffering off;所以大文件上传、流式传输、SSE、长连接接口,通常要主动评估这两个开关,而不是只套一个通用模板。
代理响应里的重定向和 Cookie 有时也要改
很多后端服务只知道自己运行在内网地址或子路径下,这时它返回的 Location、Set-Cookie 往往不适合直接发给客户端。
改写重定向地址
nginx
location /one/ {
proxy_pass http://upstream:port/two/;
proxy_redirect default;
}或者显式写:
nginx
proxy_redirect http://localhost:8000/ /;改写 Cookie 域名
nginx
proxy_cookie_domain localhost example.org;把后端返回的:
text
domain=localhost改成:
text
domain=example.org改写 Cookie 路径
nginx
proxy_cookie_path /two/ /;给 Cookie 加安全属性
nginx
proxy_cookie_flags ~ secure httponly samesite=lax;如果你的站点是“外部访问路径”和“后端真实路径”不一致,Cookie 和重定向改写经常是必须项,不是锦上添花。
动态域名代理时别忘了 resolver
如果 proxy_pass 里用了变量,或者域名是在运行期解析的,就要显式配置 resolver。
nginx
resolver 127.0.0.1 valid=30s;
location / {
proxy_pass http://$target_host$request_uri;
}如果没有 resolver,这类配置很容易在启动后解析失败或不按预期更新。
upstream 和负载分发
最基础的上游组
nginx
upstream backend {
server 127.0.0.1:3000 weight=3;
server 127.0.0.1:3001;
server 127.0.0.1:3002 backup;
}
server {
location / {
proxy_pass http://backend;
}
}默认分发策略是加权轮询。
weight=3:权重更高,分到的请求更多backup:只有主节点不可用时才接流量
健康退避相关参数
nginx
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
}这组参数控制的是:
- 在一定时间内失败多少次,认为这个节点暂时不可用
- 暂时不可用要持续多久
注意一个关键点:
“什么叫失败”,和 proxy_next_upstream 这类指令有关,不是只有 TCP 断开才算失败。
上游长连接
nginx
upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
keepalive 32;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}上游长连接的价值是减少反复建连。
这类配置建议显式写,不要只依赖不同版本的默认值。
什么时候考虑 least_conn、ip_hash、hash
- 请求处理时间差异很大时,可以考虑
least_conn - 需要按客户端地址做相对稳定分发时,可以考虑
ip_hash - 需要按某个变量做稳定路由时,可以考虑
hash
这几种不是谁更高级,而是谁更贴合你的流量特征。
缓存、压缩和响应头
反向代理缓存
一个最小可用的代理缓存通常分两层:
- 在
http层声明缓存目录和共享内存区 - 在
location层决定哪些请求进缓存、缓存多久、哪些情况绕过
nginx
http {
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=page_cache:20m inactive=30m max_size=2g use_temp_path=off;
server {
location / {
proxy_pass http://backend;
proxy_cache page_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_lock on;
proxy_cache_background_update on;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_bypass $http_authorization $cookie_session;
proxy_no_cache $http_authorization $cookie_session;
}
}
}这里最关键的不是记全所有指令,而是理解它们分工:
| 指令 | 作用 |
|---|---|
proxy_cache_path | 定义缓存目录、共享内存区、容量和清理策略 |
proxy_cache | 在当前层启用哪个缓存区 |
proxy_cache_key | 用什么当缓存键 |
proxy_cache_valid | 不同状态码缓存多久 |
proxy_cache_bypass | 这次请求不读缓存 |
proxy_no_cache | 这次响应不写缓存 |
proxy_cache_lock | 同一个新键只让一个请求回源填缓存 |
proxy_cache_background_update | 后台刷新过期缓存 |
proxy_cache_use_stale | 上游出错时是否继续回旧缓存 |
几个容易忽略的事实:
keys_zone存的是缓存键和元信息,不是响应体本身inactive到期会清理长时间没访问的数据,不看它是否“还新鲜”- 如果响应带
Set-Cookie,默认通常不会被缓存 - 如果要让
HEAD和GET区分开,缓存键要包含请求方法
静态资源缓存和响应头
nginx
location /assets/ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable" always;
}这里要注意两个细节:
add_header默认不是所有状态码都加,想更稳地覆盖错误页或特殊响应,通常要加always- 如果你在子层重新写了
add_header,上层的add_header不会自动帮你合并
这也是很多人明明在 server 层加了安全头,结果某个 location 里又写了一次 add_header 后,上层头全没了的原因。
gzip 压缩
nginx
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
gzip_vary on;这几项比较关键:
gzip:总开关gzip_min_length:太小的响应不压gzip_types:除text/html之外,还要压哪些 MIME 类型gzip_vary on:让缓存体系知道压缩是有区分的
另外要记一条风险:
- 在 TLS 场景下,压缩响应可能受到 BREACH 这类攻击影响
所以涉及敏感反射内容时,不要把“开压缩”当成无脑优化项。
TLS、真实 IP 和访问控制
HTTPS 的稳定写法
nginx
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://backend;
}
}几个点值得单独记住:
- 旧时代的
ssl on;已经过时,直接在listen上写ssl - 证书文件里如果要带中间证书,顺序应正确
- 同一个站点可以同时加载不同类型的证书,比如 RSA 和 ECDSA
- 如果开
ssl_stapling,还要考虑resolver和ssl_trusted_certificate
代理链里的真实客户端 IP
如果 Nginx 前面还有 CDN、SLB、四层代理,直接用 $remote_addr 往往看到的是上一跳,不是真实客户端。
nginx
set_real_ip_from 192.168.1.0/24;
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;这里最关键的不是把功能开起来,而是只信任你真的控制的上游地址。
real_ip_recursive on; 的意义是:
- 从
X-Forwarded-For链里继续往前找 - 找到最后一个不在信任名单里的地址,作为真实客户端地址
这类配置如果信任范围写太大,相当于把客户端 IP 的解释权交给了外部请求头。
基于 IP 和密码的访问控制
IP 白名单
nginx
location /internal/ {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
}规则按顺序检查,遇到第一个匹配就结束。
Basic Auth
nginx
location /admin/ {
auth_basic "closed site";
auth_basic_user_file /etc/nginx/htpasswd;
}组合使用
nginx
location /admin/ {
satisfy any;
allow 10.0.0.0/8;
deny all;
auth_basic "closed site";
auth_basic_user_file /etc/nginx/htpasswd;
}satisfy any 表示:
- IP 白名单通过,或者
- Basic Auth 通过
满足其一即可。
限流和连接数控制
很多人把“限流”理解成一个开关,但 Nginx 里至少要分两种:
limit_req:限制请求处理速率limit_conn:限制并发连接数
这两者不是同一件事。
limit_req:按速率控请求
nginx
http {
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
server {
location /login/ {
limit_req zone=perip burst=10 nodelay;
}
}
}这段配置的意思是:
- 以客户端 IP 作为键
- 平均速率限制为每秒
5个请求 - 允许短时突发
10个请求 nodelay表示突发请求不排队,直接按桶容量判断
几个关键点:
burst不是把平均速率改大,而是允许短时超出平均值- 不写
nodelay时,超出平均速率但还没超过burst的请求会被延迟 limit_req_status可以改被拒请求的状态码,默认通常是503limit_req_dry_run on;可以先观察,不真正拦截
limit_conn:按并发控连接
nginx
http {
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location /download/ {
limit_conn addr 2;
}
}
}这表示:
- 同一个 IP 同时最多保留
2个连接
要特别注意官方文档里的一个细节:
- 在 HTTP/2 和 HTTP/3 里,每个并发请求都按一个独立连接计数
所以如果你把 limit_conn 配得很小,HTTP/2 场景下比你想象中更容易命中限制。
什么时候用哪种
| 场景 | 更适合 |
|---|---|
| 登录、短信验证码、搜索接口防刷 | limit_req |
| 下载、大文件、长连接接口防占满 | limit_conn |
| 想先观察影响,不立刻封 | *_dry_run on |
常见误区
- 只配
limit_conn,却以为已经挡住了高频请求 - 只配
limit_req,却放任慢连接长时间占资源 - 直接用
$remote_addr做键,而前面还有代理层,结果限流对象全错
用 map 做条件分流,通常比把判断写满 if 更稳
map 的作用是根据一个变量算出另一个变量。
nginx
map $http_host $is_admin {
default 0;
admin.example.com 1;
}或者:
nginx
map $http_user_agent $is_mobile {
default 0;
"~Opera Mini" 1;
}它适合做这些事:
- 按域名切分站点逻辑
- 按请求头决定缓存或限流开关
- 给代理、日志、重定向算派生变量
官方文档还提到一个很关键的特性:
map变量是用到时才计算
所以就算你定义了不少 map,只要没被请求真正用到,不会平白增加每个请求的处理成本。
相比之下,if 更适合简单的返回、限速、少量条件改写,不适合承载整套分流逻辑。
监控、日志和排错
基础访问日志
nginx
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;这里最常用的字段里,$request_time 非常有价值,因为它能直观看到请求耗时。
条件记录日志
如果你不想让 2xx、3xx 把日志刷满,可以配条件日志:
nginx
map $status $loggable {
~^[23] 0;
default 1;
}
access_log /var/log/nginx/access.log main if=$loggable;stub_status
如果只是想快速看连接数和请求总量,可以开一个状态页:
nginx
location = /basic_status {
stub_status;
allow 127.0.0.1;
deny all;
}它能看到这些基础数据:
- Active connections
- accepts
- handled
- requests
- Reading
- Writing
- Waiting
要注意:
stub_status模块不是所有构建都默认带- 这类页面应该限制来源,不要直接裸露到公网
改配置时的最稳顺序
bash
nginx -t
nginx -s reload或者系统服务方式:
bash
sudo nginx -t
sudo systemctl reload nginx如果只是记一个排错顺序,记这个:
- 看
nginx -t是否通过 - 看请求是否真的进来了
- 看
error_log - 如果是代理,再绕过 Nginx 直接打上游
stream:Nginx 不只会代理 HTTP
很多人只把 Nginx 当 HTTP 反向代理,但它也可以做 TCP/UDP 代理,这部分在 stream 下。
nginx
stream {
upstream mysql_backend {
server 10.0.0.11:3306;
server 10.0.0.12:3306;
}
server {
listen 3306;
proxy_connect_timeout 1s;
proxy_timeout 30s;
proxy_pass mysql_backend;
}
}适合这些场景:
- MySQL
- Redis
- 自定义 TCP 服务
- DNS(UDP)
这套配置和 http 的区别很大:
- 没有
location - 没有 HTTP 头部和 URI
- 主要按端口、协议、连接层参数处理
另外要注意:
stream模块不是所有构建默认启用
一组更完整的站点示例
下面这份配置把几个常见点放到一起:
nginx
worker_processes auto;
events {
worker_connections 2048;
}
http {
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=page_cache:20m inactive=30m max_size=2g use_temp_path=off;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream app_backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
root /srv/www/app/dist;
index index.html;
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log warn;
location /assets/ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable" always;
try_files $uri =404;
}
location /api/ {
proxy_pass http://app_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 3s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_buffering on;
proxy_request_buffering on;
}
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 60s;
proxy_buffering off;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}这份配置表达的是:
80全跳 HTTPS- 静态资源强缓存
/api/走反向代理/ws/走 WebSocket- 其他路径走前端 SPA 回退
最容易踩的坑
1. root 和 alias 混着理解
现象通常是:
- 文件明明存在,但回
404 - 路径总是多一层或少一层
处理方式:
- 先看当前
location的意图是“拼接路径”还是“替换前缀”
2. proxy_pass 末尾斜杠没想清楚
这是反向代理里最常见的坑。
一多一个 /,上游收到的路径就可能变掉。
3. 正则 location 里还给 proxy_pass 带 URI
这类配置最容易出现路径替换和预期不一致。
正则 location 下通常优先让 proxy_pass 不带 URI。
4. location /foo/ 自动补斜杠
很多人以为是浏览器在跳,其实是 Nginx 的特殊处理。
5. 在子层写了一个 add_header,结果上层头没了
add_header 不是自动叠加的。
只要当前层定义了自己的 add_header,上层的通常不会自动合并进来。
6. 代理链真实 IP 不对,却先去改应用代码
如果前面有 CDN 或负载均衡,先看 set_real_ip_from、real_ip_header、real_ip_recursive。
7. 动态代理域名没配 resolver
现象通常是:
- 变量形式的
proxy_pass解析异常 - 域名变化后不按预期生效
8. index 触发内部跳转后,被另一个 location 接管
这不是偶发,是 index 的正常行为。
参考链接
- Beginner’s Guide
- How nginx processes a request
- ngx_http_core_module
- ngx_http_proxy_module
- ngx_http_upstream_module
- ngx_http_map_module
- ngx_http_limit_req_module
- ngx_http_limit_conn_module
- ngx_http_log_module
- ngx_http_headers_module
- ngx_http_ssl_module
- ngx_http_realip_module
- ngx_http_auth_basic_module
- ngx_http_access_module
- ngx_http_gzip_module
- ngx_stream_core_module
- ngx_stream_proxy_module