Nginx location 匹配与 rewrite:规则写错就 404

Nginx 配置里最容易出问题的就是 location 匹配。规则写错的表现是「明明文件在,就是 404」。

一、四种匹配方式

nginx
location = /exact { }        # 精确匹配,优先级最高
location ^~ /static/ { }     # 前缀匹配,命中后不再查正则
location ~ .php$ { }        # 正则匹配(区分大小写)
location ~* .(png|jpg)$ { } # 正则匹配(忽略大小写)
location /general { }        # 普通前缀匹配,优先级最低

优先级顺序:

bash
1. =        精确匹配(命中即停止)
2. ^~       前缀匹配(命中即停止,不再看正则)
3. ~  ~*    按配置文件中出现的先后顺序,第一个命中即停止
4. 普通前缀  选「最长」的那个,然后还要继续查正则

关键理解:普通前缀匹配不会立即停止,它会记住最长的那个,然后继续检查正则;如果正则命中,正则会覆盖它。而 ^~= 会立即停止。

二、例子:为什么我的规则没生效

nginx
location /api {
    proxy_pass http://127.0.0.1:3000;
}

location ~ ^/api/(v1|v2)/ {
    proxy_pass http://127.0.0.1:4000;
}

# 请求 /api/v1/user
# → 两个都匹配,正则优先级更高 → 走 4000

如果不想要这个行为,给前缀加 ^~

nginx
location ^~ /api {
    proxy_pass http://127.0.0.1:3000;   # 现在 /api/v1/user 也走 3000
}

三、root 与 alias(404 高发区)

指令拼接方式
root完整 URI 拼到 root 后面
alias用 alias 路径替换 location 匹配的部分
nginx
location /static/ {
    root /var/www/baize;
}
# 请求 /static/a.css → /var/www/baize/static/a.css

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

别名目录(alias)要带结尾斜杠,否则路径拼接会少一层。

常见错误:

nginx
# 错误:alias 在正则 location 里要写捕获组
location ~ ^/img/(.+\.png)$ {
    alias /data/img/$1;        # 正确
    # alias /data/img;         ← 错误
}

四、rewrite 的四个 flag

nginx
rewrite ^/old/(.*)$ /new/$1 last;
rewrite ^/old/(.*)$ /new/$1 break;
rewrite ^/old/(.*)$ /new/$1 redirect;    # 302
rewrite ^/old/(.*)$ /new/$1 permanent;   # 301
flag行为
last重写后重新走一遍 location 匹配
break重写后停止 rewrite 阶段,在当前 location 继续处理
redirect返回 302 临时重定向给浏览器
permanent返回 301 永久重定向给浏览器

lastbreak 的区别是面试常问,也是实际最容易搞混的:last 会重新匹配 location(可能走到另一个 location),break 不会。

死循环风险last 如果重写后又匹配回同一个 location,会循环 10 次然后报 500。

五、常见场景

强制 HTTPS

nginx
server {
    listen 80;
    server_name bzii.cn www.bzii.cn;
    return 301 https://www.bzii.cn$request_uri;
}

returnrewrite 更高效,也更清晰。能用 return 就别用 rewrite。

统一 www

nginx
server {
    listen 443 ssl;
    server_name www.bzii.cn;
    return 301 https://www.bzii.cn$request_uri;
}

去掉 .html 后缀

nginx
location / {
    try_files $uri $uri.html $uri/ =404;
}

# 或更友好:重定向到无后缀版本
if ($request_uri ~ ^/(.*)\.html$) {
    return 301 /$1;
}

SPA 回退

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

旧链接迁移(多组规则)

nginx
rewrite ^/blog/(\d+)/(\d+)/(.+)$ /posts/$3 permanent;
rewrite ^/tag/(.*)$ /tags/$1 permanent;
rewrite ^/feed$ /feed.xml permanent;

六、try_files 是更现代的选择

nginx
location / {
    try_files $uri $uri/ $uri.html /index.html =404;
}

location /images/ {
    try_files $uri /images/default.png;
}

try_files 按顺序尝试,都不存在就用最后一个(可以是命名 location 或状态码)。比 rewrite 直观。

七、404 排查流程

bash
# 1. 看是哪个 location 在处理(加日志)
log_format dbg '$request_uri → $uri → $document_root → $status';

# 2. 确认文件真的存在且可读
ls -l /var/www/baize/static/a.css
sudo -u www-data test -r /var/www/baize/static/a.css && echo OK

# 3. 看错误日志(会写出实际尝试的路径)
tail -5 /var/log/nginx/error.log
# 典型:open() "/var/www/baize/static/a.css" failed (2: No such file or directory)

# 4. 确认配置生效
sudo nginx -T | grep -A5 "location"

错误日志里的路径是最直接的线索:看它实际去哪找文件了,立刻就知道 root/alias 配错在哪。

八、403 的常见原因

原因检查
目录没有 x 权限namei -l /var/www/baize/x
文件没有 r 权限ls -l
autoindex 关闭且无 index 文件加 index 或开 autoindex
被 deny 规则挡住检查 deny/allow

九、检查清单

  • location 优先级符合预期(正则会覆盖普通前缀)
  • root 与 alias 用对了(斜杠是否匹配)
  • rewrite 用了正确的 flag
  • 没有 rewrite 死循环
  • nginx -t 通过
  • 改完 nginx -s reload

location 的规则不多,但优先级和 root/alias 这两处几乎占了所有「莫名其妙 404」的成因。