Nginx location 配置说明(后端开发版)
本文面向后端开发人员,重点说明 Nginx 如何选择 location、静态文件如何映射、请求如何转发到后端,以及常见配置风险。
1. 先看原配置
nginx
location ~ \.(js|css|png|jpg|jpeg|gif|swf|ico|bmp|avi|rm|rmvb|mp4|mp3|rar|zip|iso|doc|docx|xls|xlsx|ppt|pptx|pdf|txt|xml|webp)$ {
alias /data/www/htdocs/quickly_fill/page/.output/public;
try_files $uri $uri/ /site/index.html;
}
这段配置表达的意图是:
- URI 以列出的扩展名结尾时,按静态资源处理。
- 从
/data/www/htdocs/quickly_fill/page/.output/public查找文件。 - 找不到文件时,尝试目录,最后回退到
/site/index.html。
但它有三个值得修正的问题:
- 正则
location中的alias没有使用捕获变量。 Nginx 官方要求正则location使用alias时,正则应包含捕获组,并在alias中引用捕获结果。若 URI 与磁盘目录结构一致,更适合使用root。 - 静态资源缺失时不应通常回退到 HTML。 例如
/site/app.js不存在,却返回index.html,浏览器会因 MIME 类型不符而报错,也会掩盖资源发布问题。静态资源通常应返回404。 - 全局按扩展名匹配可能截获后端接口。 例如动态接口
/api/export/report.pdf也会命中这个正则,导致请求没有进入后端。静态资源规则最好限定在/site/、/assets/等 URI 前缀下。
另外,~ 是大小写敏感的正则匹配,.JPG 不会命中;需要忽略大小写时使用 ~*。
2. Nginx 配置的基本层级
常见配置结构如下:
nginx
# 全局上下文
user nginx;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
upstream app_backend {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_backend;
}
}
}
主要层级:
| 上下文 | 作用 |
|---|---|
| 全局 | worker 数量、运行用户、日志等进程级配置 |
events |
Nginx 连接处理模型和连接数 |
http |
HTTP 公共配置、MIME、日志、压缩、上游服务等 |
upstream |
定义一个或多个后端实例及负载均衡策略 |
server |
一个虚拟主机,通常按端口和域名区分 |
location |
在一个 server 内按 URI 选择具体处理方式 |
3. location 的常用写法
3.1 普通前缀匹配
nginx
location /api/ {
proxy_pass http://app_backend;
}
匹配所有以 /api/ 开头的 URI,例如 /api/users。
3.2 精确匹配 =
nginx
location = /health {
return 200 "ok\n";
}
只匹配 /health,不匹配 /health/ 或 /health/detail。精确匹配成功后立即停止查找。
3.3 优先前缀匹配 ^~
nginx
location ^~ /api/ {
proxy_pass http://app_backend;
}
先按前缀匹配;如果它是最长匹配前缀,Nginx 不再检查同级正则 location。当 server 中存在全局扩展名正则,并且必须保证 /api/ 始终进入后端时很有用。
3.4 区分大小写的正则 ~
nginx
location ~ \.php$ {
# 只匹配小写 .php
}
3.5 不区分大小写的正则 ~*
nginx
location ~* \.(jpg|jpeg|png|webp)$ {
# .jpg、.JPG 等都可匹配
}
3.6 命名 location
nginx
location / {
try_files $uri @backend;
}
location @backend {
proxy_pass http://app_backend;
}
命名 location 不直接匹配外部 URI,一般作为 try_files、error_page 等指令的内部跳转目标。
4. location 的匹配优先级
可按下面的流程理解:
#mermaid-svg-PNmgjKjrHirUpyRm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PNmgjKjrHirUpyRm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PNmgjKjrHirUpyRm .error-icon{fill:#552222;}#mermaid-svg-PNmgjKjrHirUpyRm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PNmgjKjrHirUpyRm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm .marker.cross{stroke:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PNmgjKjrHirUpyRm p{margin:0;}#mermaid-svg-PNmgjKjrHirUpyRm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster-label text{fill:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster-label span{color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster-label span p{background-color:transparent;}#mermaid-svg-PNmgjKjrHirUpyRm .label text,#mermaid-svg-PNmgjKjrHirUpyRm span{fill:#333;color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .node rect,#mermaid-svg-PNmgjKjrHirUpyRm .node circle,#mermaid-svg-PNmgjKjrHirUpyRm .node ellipse,#mermaid-svg-PNmgjKjrHirUpyRm .node polygon,#mermaid-svg-PNmgjKjrHirUpyRm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .rough-node .label text,#mermaid-svg-PNmgjKjrHirUpyRm .node .label text,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape .label,#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape .label{text-anchor:middle;}#mermaid-svg-PNmgjKjrHirUpyRm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .rough-node .label,#mermaid-svg-PNmgjKjrHirUpyRm .node .label,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape .label,#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape .label{text-align:center;}#mermaid-svg-PNmgjKjrHirUpyRm .node.clickable{cursor:pointer;}#mermaid-svg-PNmgjKjrHirUpyRm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm .arrowheadPath{fill:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PNmgjKjrHirUpyRm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PNmgjKjrHirUpyRm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PNmgjKjrHirUpyRm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PNmgjKjrHirUpyRm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PNmgjKjrHirUpyRm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PNmgjKjrHirUpyRm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster text{fill:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster span{color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PNmgjKjrHirUpyRm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PNmgjKjrHirUpyRm rect.text{fill:none;stroke-width:0;}#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape p,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape .label rect,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PNmgjKjrHirUpyRm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PNmgjKjrHirUpyRm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PNmgjKjrHirUpyRm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
是
否
是
否
收到请求并规范化 URI
是否命中精确匹配
location = URI
使用精确 location
寻找最长的前缀 location
最长前缀是否带 ^~
使用该前缀 location
按配置出现顺序检查正则 location
是否有正则命中
使用第一个命中的正则 location
是否存在前缀匹配
使用 server 级配置或返回错误
执行静态文件、代理、返回值等处理
是否产生内部重定向
返回响应
可以记成:
- 精确匹配
=优先。 - 先记住最长的前缀匹配。
- 最长前缀带
^~时,不再检查正则。 - 否则按配置书写顺序检查正则,使用第一个匹配项;正则不是按"最长"选择。
- 没有正则命中时,使用先前找到的最长前缀。
因此,正则 location 的书写顺序非常重要,应把更具体的正则写在更通用的正则之前。
5. root 与 alias 的区别
5.1 root:把完整 URI 拼到目录后
nginx
location /site/ {
root /data/www/htdocs/quickly_fill/page/.output/public;
}
请求与文件的映射:
text
URI: /site/assets/app.js
root: /data/www/htdocs/quickly_fill/page/.output/public
文件路径: /data/www/htdocs/quickly_fill/page/.output/public/site/assets/app.js
当 URI 目录结构和磁盘目录结构一致时,优先使用 root。
5.2 alias:用目标目录替换匹配到的 URI 前缀
nginx
location /site/ {
alias /data/www/htdocs/quickly_fill/page/.output/public/;
}
映射结果:
text
URI: /site/assets/app.js
匹配前缀: /site/
alias: /data/www/htdocs/quickly_fill/page/.output/public/
文件路径: /data/www/htdocs/quickly_fill/page/.output/public/assets/app.js
前缀 location 使用 alias 时,路径末尾的 / 最好与 location 保持对应,避免拼接结果异常。
5.3 正则 location 使用 alias
必须显式捕获需要映射的路径:
nginx
location ~* ^/downloads/(?<file_path>.+\.(?:pdf|zip))$ {
alias /data/downloads/$file_path;
}
请求 /downloads/manual/guide.pdf 映射为 /data/downloads/manual/guide.pdf。
正则 alias 可读性和维护成本都更高。没有必须替换路径前缀的需求时,建议使用 root。
6. try_files 如何工作
nginx
try_files $uri $uri/ /site/index.html;
Nginx 按顺序检查:
$uri对应的文件是否存在。$uri/对应的目录是否存在。- 前面都不存在时,对
/site/index.html发起内部重定向,重新执行一次location匹配。
最后一个参数有特殊意义:
nginx
# 找不到就返回 404
try_files $uri =404;
# 找不到就进入命名 location
try_files $uri @backend;
# SPA 路由找不到实际文件时回退到入口页
try_files $uri $uri/ /site/index.html;
try_files 使用当前 root 或 alias 计算物理路径。配置复杂时,可通过 error_log 和 $request_filename 日志字段确认实际查找路径。
7. 推荐配置:静态站点 + SPA + 后端 API
以下示例假设:
- 前端访问前缀为
/site/。 - 磁盘上存在
/data/www/htdocs/quickly_fill/page/.output/public/site/index.html。 - 后端监听
127.0.0.1:8080。 /api/原样传递给后端。
nginx
upstream quickly_fill_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name example.com;
# 后端接口优先处理,避免被同级正则静态规则截获。
location ^~ /api/ {
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;
# 没有结尾斜杠:后端收到原始 URI,例如 /api/users。
proxy_pass http://quickly_fill_backend;
}
# 对真实静态资源单独处理;文件不存在时明确返回 404。
location ~* ^/site/.+\.(?:js|css|png|jpe?g|gif|ico|bmp|mp4|mp3|zip|pdf|webp)$ {
root /data/www/htdocs/quickly_fill/page/.output/public;
try_files $uri =404;
expires 7d;
add_header Cache-Control "public" always;
access_log off;
}
# 没有扩展名的前端路由回退到 SPA 入口页。
location /site/ {
root /data/www/htdocs/quickly_fill/page/.output/public;
try_files $uri $uri/ /site/index.html;
}
# 是否跳转到 /site/ 可按项目入口需求调整。
location = / {
return 302 /site/;
}
}
这里故意把"静态资源 404"和"SPA 路由回退"拆开:
#mermaid-svg-M17hnjXQ21S3xzXe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M17hnjXQ21S3xzXe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M17hnjXQ21S3xzXe .error-icon{fill:#552222;}#mermaid-svg-M17hnjXQ21S3xzXe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M17hnjXQ21S3xzXe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe .marker.cross{stroke:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M17hnjXQ21S3xzXe p{margin:0;}#mermaid-svg-M17hnjXQ21S3xzXe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster-label text{fill:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster-label span{color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster-label span p{background-color:transparent;}#mermaid-svg-M17hnjXQ21S3xzXe .label text,#mermaid-svg-M17hnjXQ21S3xzXe span{fill:#333;color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .node rect,#mermaid-svg-M17hnjXQ21S3xzXe .node circle,#mermaid-svg-M17hnjXQ21S3xzXe .node ellipse,#mermaid-svg-M17hnjXQ21S3xzXe .node polygon,#mermaid-svg-M17hnjXQ21S3xzXe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .rough-node .label text,#mermaid-svg-M17hnjXQ21S3xzXe .node .label text,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape .label,#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape .label{text-anchor:middle;}#mermaid-svg-M17hnjXQ21S3xzXe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .rough-node .label,#mermaid-svg-M17hnjXQ21S3xzXe .node .label,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape .label,#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape .label{text-align:center;}#mermaid-svg-M17hnjXQ21S3xzXe .node.clickable{cursor:pointer;}#mermaid-svg-M17hnjXQ21S3xzXe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe .arrowheadPath{fill:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M17hnjXQ21S3xzXe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M17hnjXQ21S3xzXe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M17hnjXQ21S3xzXe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-M17hnjXQ21S3xzXe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M17hnjXQ21S3xzXe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-M17hnjXQ21S3xzXe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster text{fill:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster span{color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M17hnjXQ21S3xzXe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-M17hnjXQ21S3xzXe rect.text{fill:none;stroke-width:0;}#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape p,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape .label rect,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M17hnjXQ21S3xzXe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-M17hnjXQ21S3xzXe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-M17hnjXQ21S3xzXe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} /api/
/site/
是
是
否
否
是
否
请求
URI 前缀
反向代理到后端
是否为静态资源扩展名
文件存在吗
返回静态文件
返回 404
文件或目录存在吗
返回文件或目录入口
内部跳转到 /site/index.html
如果真实入口文件是:
text
/data/www/htdocs/quickly_fill/page/.output/public/index.html
也就是磁盘上没有 site 目录,那么需要用 alias 去掉 URI 中的 /site/ 前缀。为了避免缺失的静态资源回退成 HTML,可以拆成三条规则:
nginx
# 正则 location 使用 alias 时,通过命名捕获组构造物理路径。
location ~* ^/site/(?<asset_path>.+\.(?:js|css|png|jpe?g|gif|ico|bmp|mp4|mp3|zip|pdf|webp))$ {
alias /data/www/htdocs/quickly_fill/page/.output/public/$asset_path;
expires 7d;
add_header Cache-Control "public" always;
}
# SPA 入口页单独精确映射,并避免缓存旧入口。
location = /site/index.html {
alias /data/www/htdocs/quickly_fill/page/.output/public/index.html;
add_header Cache-Control "no-cache" always;
}
# 页面路由对应的文件不存在时,才回退到 SPA 入口页。
location /site/ {
alias /data/www/htdocs/quickly_fill/page/.output/public/;
error_page 404 =200 /site/index.html;
}
error_page 404 =200 会把当前 location 产生的 404 内部重定向到 /site/index.html,并将最终状态码改为 200。上线前必须按实际目录结构验证,不应仅根据 URI 猜测文件位置。若可以调整发布目录结构,仍优先推荐前面的 root 方案。
8. 后端反向代理的常见配置
8.1 proxy_pass 是否带结尾 /
这是最常见的路径问题之一。
nginx
location /api/ {
proxy_pass http://127.0.0.1:8080;
}
请求 /api/users?id=1,后端收到 /api/users?id=1。
nginx
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
请求 /api/users?id=1,匹配前缀 /api/ 被替换为 /,后端收到 /users?id=1。
可以理解为:
| 配置 | 上游收到的路径 |
|---|---|
proxy_pass http://backend; |
保留原始 URI |
proxy_pass http://backend/; |
用 / 替换匹配到的普通前缀 |
正则 location、命名 location 和发生 URI 重写的场景限制更多,不建议依赖模糊的路径替换行为,应明确设计上下游路径契约。
8.2 常用代理请求头
nginx
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 |
用户访问的域名 |
X-Real-IP |
直接连接到 Nginx 的客户端 IP |
X-Forwarded-For |
经过多层代理后的 IP 链 |
X-Forwarded-Proto |
原始请求协议,通常为 http 或 https |
后端只有在请求确实来自可信代理时,才应信任 X-Forwarded-*。应用直接暴露公网时,客户端可以伪造这些请求头。
8.3 WebSocket
map 应放在 http 上下文中:
nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
代理位置中增加:
nginx
location /ws/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://quickly_fill_backend;
}
9. 常见业务场景模板
9.1 所有请求都交给后端
nginx
location / {
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_pass http://app_backend;
}
9.2 静态文件优先,不存在时交给后端
nginx
location / {
root /data/www/public;
try_files $uri $uri/ @backend;
}
location @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_pass http://app_backend;
}
9.3 独立资源目录
nginx
location /assets/ {
alias /data/releases/current/assets/;
expires 30d;
}
静态文件不存在时,Nginx 的静态文件处理模块会直接返回 404,不必额外写 try_files。若需要多级回退,优先调整发布目录结构后改用 root,并用调试日志核对 $request_filename。
9.4 上传文件下载
nginx
location /uploads/ {
alias /data/app/uploads/;
# 不允许浏览目录列表。
autoindex off;
# 降低用户上传内容被浏览器当作页面执行的风险。
add_header X-Content-Type-Options nosniff always;
}
用户上传目录不要允许执行脚本;上传文件名、扩展名、MIME 和访问权限仍应由应用层校验。
10. 容易踩坑的配置
10.1 全局扩展名正则覆盖 API
nginx
location /api/ {
proxy_pass http://app_backend;
}
location ~* \.pdf$ {
root /data/www/public;
}
/api/report.pdf 可能被后面的正则规则处理。解决方法是把静态正则限定为 ^/site/,或在 API 前缀上使用 ^~。
10.2 缺失的 JavaScript 返回 HTML
nginx
location ~* \.js$ {
try_files $uri /index.html;
}
这会导致浏览器出现类似错误:
text
Refused to execute script because its MIME type ('text/html') is not executable.
静态资源规则应使用 try_files $uri =404;,SPA 的 HTML 回退放到页面路由规则中。
10.3 URI 尾部斜杠不一致
location /site/ 不匹配 /site。可增加精确跳转:
nginx
location = /site {
return 301 /site/;
}
10.4 忘记加载 MIME 类型
http 中通常需要:
nginx
include mime.types;
default_type application/octet-stream;
否则 CSS、JavaScript、字体等资源可能返回不正确的 Content-Type。
10.5 缓存 HTML 入口页
带内容哈希的静态资源可以长期缓存,但 index.html 一般应短缓存或不缓存,否则发布后用户可能继续引用旧资源:
nginx
location = /site/index.html {
root /data/www/htdocs/quickly_fill/page/.output/public;
add_header Cache-Control "no-cache" always;
}
11. 检查、发布与排障
修改后先检查语法:
bash
nginx -t
语法通过后平滑加载:
bash
nginx -s reload
使用 systemd 的系统通常执行:
bash
sudo systemctl reload nginx
查看最终合并后的完整配置:
bash
nginx -T
验证请求:
bash
curl -I http://example.com/site/assets/app.js
curl -I http://example.com/site/not-found.js
curl -i http://example.com/site/client-side-route
curl -i http://example.com/api/health
预期结果:
| 请求 | 预期 |
|---|---|
| 已存在的静态资源 | 200,且 Content-Type 正确 |
不存在的 .js/图片 |
404,不是 index.html |
| SPA 前端路由 | 200,返回 index.html |
| API | 后端实际状态码和响应体 |
需要确认物理文件路径时,可临时增加包含 $request_filename 的日志格式:
nginx
log_format location_debug '$remote_addr "$request" status=$status '
'uri=$uri file=$request_filename';
access_log /var/log/nginx/location_debug.log location_debug;
确认完成后应撤销临时高频调试日志,避免无意义的磁盘占用。
12. 针对原配置的结论
如果 /site/assets/app.js 对应的真实文件是:
text
/data/www/htdocs/quickly_fill/page/.output/public/site/assets/app.js
建议改为:
nginx
location ~* ^/site/.+\.(?:js|css|png|jpe?g|gif|swf|ico|bmp|avi|rm|rmvb|mp4|mp3|rar|zip|iso|docx?|xlsx?|pptx?|pdf|txt|xml|webp)$ {
root /data/www/htdocs/quickly_fill/page/.output/public;
try_files $uri =404;
}
location /site/ {
root /data/www/htdocs/quickly_fill/page/.output/public;
try_files $uri $uri/ /site/index.html;
}
如果真实文件是:
text
/data/www/htdocs/quickly_fill/page/.output/public/assets/app.js
也就是需要去掉 URI 中的 /site/,则使用带捕获组的正则 alias 处理静态资源,并将 SPA 页面回退分开配置:
nginx
location ~* ^/site/(?<asset_path>.+\.(?:js|css|png|jpe?g|gif|swf|ico|bmp|avi|rm|rmvb|mp4|mp3|rar|zip|iso|docx?|xlsx?|pptx?|pdf|txt|xml|webp))$ {
alias /data/www/htdocs/quickly_fill/page/.output/public/$asset_path;
}
location = /site/index.html {
alias /data/www/htdocs/quickly_fill/page/.output/public/index.html;
add_header Cache-Control "no-cache" always;
}
location /site/ {
alias /data/www/htdocs/quickly_fill/page/.output/public/;
error_page 404 =200 /site/index.html;
}
最终选择不能只看现有片段,必须同时确认两件事:浏览器请求的 URI,以及服务器上资源的真实绝对路径。