Nginx 从入门到精通 / 04 - Location 路由匹配 / Location Matching
Location 路由匹配 / Location Matching
🟢 基础 / Basics — Location 语法
四种匹配前缀
# 1. 精确匹配 (=)
location = /login {
# 只匹配 /login,不匹配 /login/ 或 /login/page
}
# 2. 前缀匹配 (^~) — 优先于正则
location ^~ /static/ {
# 匹配以 /static/ 开头的路径
# 一旦匹配,不再检查正则
}
# 3. 正则匹配 (~ 区分大小写, ~* 不区分大小写)
location ~* \.(jpg|png|gif)$ {
# 匹配图片文件(不区分大小写)
}
# 4. 普通前缀匹配(无修饰符)
location /api/ {
# 匹配以 /api/ 开头的所有路径
}
匹配优先级(最重要!)
请求: GET /static/js/app.js
优先级顺序(从高到低):
1. 精确匹配 = /static/js/app.js → 精确命中则直接使用,停止搜索
2. 前缀匹配 ^~ /static/ → 命中最长前缀后,停止搜索(不查正则)
3. 正则匹配 ~ \.js$ → 按配置文件中的顺序,第一个匹配的生效
4. 普通前缀匹配 /static/ → 命中最长前缀,但继续搜索正则
5. 默认 / → 兜底
记忆口诀 / Mnemonic:
= (精确) > ^~ (前缀优先) > ~ (正则顺序) > / (前缀最长)
优先级实战演示
server {
location = / {
return 200 "精确匹配: 只匹配根路径";
}
location / {
return 200 "普通前缀: 兜底";
}
location /doc {
return 200 "普通前缀: /doc";
}
location ^~ /doc/ {
return 200 "前缀优先: /doc/";
}
location ~* \.html$ {
return 200 "正则: .html 文件";
}
}
请求 命中的 location 原因
/ = / 精确匹配
/index.html ~* \.html$ 正则匹配(/ 兜底被正则覆盖)
/doc /doc 最长前缀匹配
/doc/ ^~ /doc/ ^~ 前缀优先,停止正则搜索
/doc/page.html ^~ /doc/ ^~ 前缀优先,即使正则能匹配
/about / 兜底前缀匹配
/about.html ~* \.html$ 正则匹配
🟡 进阶 / Intermediate — Rewrite 与正则捕获
rewrite 指令
# 语法: rewrite regex replacement [flag];
# flag 说明:
# last — 停止当前 rewrite,重新发起 location 匹配(类似 continue)
# break — 停止当前 rewrite,直接在当前 location 执行(不再重新匹配)
# redirect — 返回 302 临时重定向
# permanent — 返回 301 永久重定向
rewrite 实战
server {
server_name example.com;
# 1. 永久重定向:旧路径 → 新路径
rewrite ^/old-page$ /new-page permanent; # 301
# 2. 临时重定向
rewrite ^/maintenance$ /coming-soon redirect; # 302
# 3. 正则捕获($1, $2...)
rewrite ^/user/(\d+)$ /profile?id=$1 last;
# 4. 带条件的重写
rewrite ^/blog/(.*)$ /articles/$1 last;
# 5. 非 URL 的 rewrite(只改参数)
if ($request_uri ~* "^/search\?q=(.*)") {
rewrite ^ /search?q=$1&lang=zh last;
}
location /profile {
# 这里会收到重写后的请求 /profile?id=123
proxy_pass http://backend;
}
location /articles {
proxy_pass http://backend;
}
}
rewrite 的 last vs break 深入对比
# 场景 A:last — 重新匹配 location
location /old/ {
rewrite ^/old/(.*)$ /new/$1 last;
# ↑ last: 跳出当前 location,重新从头匹配
# 请求会进入下面的 location /new/
}
location /new/ {
proxy_pass http://backend;
}
# 场景 B:break — 在当前 location 执行
location /static/ {
rewrite ^/static/(.*)$ /assets/$1 break;
# ↑ break: 不跳出,直接用 rewrite 后的 URI 在当前 location 执行
root /var/www; # 最终文件: /var/www/assets/xxx
}
last 执行流程:
/old/page → 匹配 location /old/ → rewrite → /new/page
→ 重新匹配 → 命中 location /new/ → proxy_pass
break 执行流程:
/static/app.js → 匹配 location /static/ → rewrite → /assets/app.js
→ 不重新匹配 → root /var/www → 文件: /var/www/assets/app.js
if 指令(慎用!)
# ⚠️ Nginx 官方不推荐在 location 中使用 if
# 但在 server 块中做简单条件判断还是可以的
# ✅ 推荐用法:
# 强制 HTTPS
if ($scheme = http) {
return 301 https://$host$request_uri;
}
# www → 裸域
if ($host = www.example.com) {
return 301 https://example.com$request_uri;
}
# 禁止特定 User-Agent
if ($http_user_agent ~* (curl|wget|python)) {
return 403;
}
# ❌ 避免的用法(容易出 bug):
# if 内使用 proxy_pass(行为不可预期)
location /api/ {
if ($arg_debug) {
proxy_pass http://debug-backend; # ⚠️ 可能不生效
}
proxy_pass http://production-backend;
}
# 更好的写法:用 map 或 split_clients
map 指令(变量映射)
http {
# 根据 User-Agent 映射变量
map $http_user_agent $is_bot {
default 0;
~*(googlebot|bingbot|yahoobot) 1;
~*(crawler|spider|bot) 1;
}
# 根据请求路径映射后端
map $uri $backend {
default http://127.0.0.1:3000;
/api/v1/ http://127.0.0.1:3001;
/api/v2/ http://127.0.0.1:3002;
/admin/ http://127.0.0.1:3003;
}
# 根据客户端 IP 映射限流区域
map $binary_remote_addr $rate_limit_zone {
default normal;
10.0.0.0/8 internal; # 内网不限流
172.16.0.0/12 internal;
}
server {
location / {
proxy_pass $backend; # 根据路径动态选择后端
# 机器人限流
if ($is_bot) {
return 429;
}
}
}
}
正则性能提示
# ❌ 性能差:在每个 location 都用复杂正则
location ~ "^/api/v[0-9]+/(users|posts|comments)/[0-9]+$" {
# ...
}
# ✅ 性能好:先用前缀匹配缩小范围
location /api/ {
# 先前缀匹配 /api/
# 再在 /api/ 内部做细分
location ~ ^/api/v(\d+)/(users|posts|comments)/(\d+)$ {
set $api_version $1;
set $resource $2;
set $id $3;
proxy_pass http://backend_v$api_version;
}
}
🔴 高级 / Advanced — 内部重定向与嵌套 Location
内部重定向指令对比
# rewrite ... last
# 跳出当前 location,重新进行完整的 location 匹配
# 可以跳到任何 location
# rewrite ... break
# 留在当前 location,使用 rewrite 后的 URI
# 不重新匹配
# try_files
# 按顺序检查文件,最后一个参数可以是 URI 或状态码
# 内部重定向
# error_page
# 可以将错误码重定向到另一个 URI
# 内部重定向
嵌套 Location 的作用域
location /api/ {
# 父级配置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 子 location 继承父级配置,可以覆盖
location /api/v1/ {
proxy_pass http://v1-backend;
# 继承 proxy_set_header
}
location /api/v2/ {
proxy_pass http://v2-backend;
proxy_set_header X-API-Version "2"; # 覆盖/新增 header
}
# 正则 location 不能嵌套(有 workaround)
location ~ ^/api/v(\d+)/uploads {
# ⚠️ 嵌套正则 location 可能导致意外行为
# 建议用 map 替代
client_max_body_size 100m;
proxy_pass http://upload-backend;
}
}
# ⚠️ 命名 location 只能被 error_page / try_files / internal 引用
location @fallback {
proxy_pass http://fallback-backend;
}
location / {
try_files $uri $uri/ @fallback; # ✅ 正确用法
}
经典 SPA + API 分离配置
server {
listen 443 ssl;
server_name app.example.com;
root /var/www/spa;
# API 请求转发到后端
location /api/ {
proxy_pass http://127.0.0.1:3000;
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;
}
# 静态资源(带 hash 的文件名,长期缓存)
location ~* \.(css|js|jpg|png|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# SPA 路由:其他所有请求返回 index.html
location / {
try_files $uri $uri/ /index.html;
# 例如请求 /dashboard/settings → 返回 index.html
# 由前端路由(React Router / Vue Router)处理
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
}
}
多级 Rewrite 实战:短链服务
server {
server_name s.example.com;
# 短链映射
location / {
# 用 map 或 Lua 查询数据库获取长链接
# 这里演示纯 Nginx 的方式
# 规则 1:/g/google → https://www.google.com
rewrite ^/g/(.+)$ https://www.google.com/search?q=$1 permanent;
# 规则 2:/r/123 → /redirect?id=123(后端处理)
rewrite ^/r/(\d+)$ /redirect?id=$1 last;
# 规则 3:/gh/user/repo → GitHub 仓库
rewrite ^/gh/([^/]+/[^/]+)$ https://github.com/$1 permanent;
# 兜底
return 404 "Short link not found";
}
location /redirect {
proxy_pass http://127.0.0.1:3000;
}
}
配置调试技巧
# 1. 开启调试日志(需要编译 --with-debug)
# /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log debug;
# 2. 查看 Nginx 的 location 匹配过程
# 在 error.log 中会看到:
# *1 using configuration "/api/v1/users"
# 这说明 Nginx 选择了哪个 location
# 3. 用 curl 测试不同路径
curl -I http://localhost/test
curl -I http://localhost/api/v1/users
curl -I http://localhost/static/file.js
# 4. 添加调试 header
add_header X-Debug-Location "matched" always;
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | 四种前缀(=, ^~, ~, /),优先级顺序 |
| 🟡 进阶 | rewrite(last vs break),if,map,正则捕获 |
| 🔴 高级 | 内部重定向,嵌套 location,SPA + API 配置,调试技巧 |