前言:为什么一定要搞懂 location 与 rewrite
很多站长在给 VPS 配置 Nginx 时,最容易踩坑的就是 location 匹配规则和 rewrite 重写指令。明明照着网上抄了一段配置,结果访问路径不是 404 就是 403,甚至出现无限重定向循环。这两者的本质都是对 URI 做处理,但一个负责「选块执行」,一个负责「改写地址」,规则互相叠加后非常容易混乱。本文从匹配优先级、常用语法、真实场景示例和排错思路四个维度,把这两个核心模块彻底讲透。
一、location 匹配规则与优先级
location 用于在 Nginx 配置中按 URI 前缀或正则选择配置块。理解优先级是排错的第一步,Nginx 官方给出的匹配顺序如下:
1. 精确匹配 location = /path 2. 前缀最长匹配 location ^~ /path 3. 正则匹配(按配置文件书写顺序,命中的第一条生效) location ~ /path (区分大小写) location ~* /path (不区分大小写) 4. 普通前缀匹配 location /path 5. 兜底匹配 location /
一个常见的误解是「配置写在前面的优先级更高」,实际上正则之间才看书写顺序,前缀匹配永远遵循「最长匹配原则」。我们用一个例子验证:
server {
listen 80;
server_name example.com;
location = / {
return 200 "exact root\n";
}
location /images/ {
return 200 "prefix images\n";
}
location ^~ /images/logo/ {
return 200 "longest prefix logo\n";
}
location ~* \.(png|jpg)$ {
return 200 "regex image\n";
}
}
访问 /images/logo/a.png 时,命中顺序是:^~ /images/logo/ 前缀最长,且带 ^~ 的普通前缀一旦匹配成功就不会再去尝试正则,因此返回 longest prefix logo。如果把 ^~ 去掉,则正则 ~* \.(png|jpg)$ 会覆盖掉最长前缀,返回 regex image。这就是「^~ 能截断正则检查」的关键点。
二、location 常用修饰符对比
| 修饰符 | 匹配方式 | 是否检查正则 | 典型用途 |
|---|---|---|---|
| = | 精确匹配 | 否 | 首页、固定路径,性能最高 |
| ^~ | 前缀匹配 | 否 | 静态资源目录,防止被正则干扰 |
| ~ | 正则(区分大小写) | 是 | 按文件类型、文件名规则分流 |
| ~* | 正则(不区分大小写) | 是 | 兼容 .JPG/.jpg 等大小写混用 |
| 无修饰符 | 前缀匹配 | 是 | 普通目录级配置 |
实际配置中强烈建议:能用前缀解决的就不要用正则,因为前缀匹配速度远快于正则匹配,且逻辑更直观。以下是一份静态资源站点的推荐写法:
location ^~ /assets/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
root /var/www/html;
expires 7d;
add_header Cache-Control "public";
try_files $uri =404;
}
三、rewrite 重写指令语法
rewrite 属于 ngx_http_rewrite_module,语法为:
rewrite 正则 替换地址 [标记];
标记共有四种,含义如下:
last 停止处理 rewrite 指令,重新用新 URI 匹配 location(内部跳转,URL 不变) break 停止处理 rewrite 指令,直接按新 URI 在当前 location 内继续执行 redirect 返回 302 临时重定向(仅当替换地址以 http:// 或 https:// 开头时生效) permanent 返回 301 永久重定向
last 与 break 是最容易混淆的一对。简单记忆:last 是「重写后重新走一遍匹配流程」,break 是「重写后停在本 location 就地执行」。看下面的对比:
# 场景 A:使用 last
location /old/ {
rewrite ^/old/(.*)$ /new/$1 last;
}
location /new/ {
return 200 "new block";
}
# 场景 B:使用 break
location /old/ {
rewrite ^/old/(.*)$ /new/$1 break;
root /var/www/html; # 不会重新匹配 /new/,URI 停留在 /new/xxx
try_files $uri =404;
}
场景 A 中访问 /old/a.html 会内部跳转到 /new/a.html 并重新匹配 location /new/,浏览器地址栏不变。场景 B 中 URI 被改成 /new/a.html 但不会重新匹配,直接在当前 location 里用 try_files 找文件。若想对外可见地改变地址,必须使用 redirect 或 permanent:
# 把 http 强制跳转 https 并保留路径
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# 整站旧路径 301 迁移
rewrite ^/article/([0-9]+)\.html$ /posts/$1 permanent;
四、if 与 rewrite 的坑:尽量用 try_files 替代
Nginx 官方文档明确警告:if 指令在 location 上下文中容易产生不可预期的行为(著名的 if is evil)。比如下面这种写法在 Nginx 旧版本中会导致 404:
# 反例:不要这样写
location / {
if (-f $request_filename) {
break;
}
if (-d $request_filename) {
break;
}
rewrite ^(.+)$ /index.php?q=$1 last;
}
推荐用 try_files 优雅替代,这也是 WordPress、Typecho 等伪静态配置的标准写法:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
配合 if 唯一可以安全使用的场景是判断 http_user_agent 做简单防爬:
location / {
if ($http_user_agent ~* "AhrefsBot|MJ12bot|SemrushBot") {
return 403;
}
try_files $uri $uri/ /index.php?$query_string;
}
五、真实场景实战:伪静态、移动端跳转与协议跳转
场景 1:Typecho 伪静态
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php$1 last;
}
}
上面是网上的常见写法,但更推荐直接用 try_files 版本:
location / {
try_files $uri $uri/ /index.php?$args;
}
场景 2:移动端与 PC 端分流跳转
server {
listen 80;
server_name www.example.com;
# 手机浏览器访问 PC 页面时 302 跳转到移动站
if ($http_user_agent ~* "(Mobile|Android|iPhone|iPad)") {
rewrite ^/(.*)$ https://m.example.com/$1 redirect;
}
location / {
root /var/www/pc;
index index.html;
}
}
场景 3:首页强制 HTTPS 并携带 HSTS
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
root /var/www/html;
index index.html;
}
}
六、常见报错与排错方法
问题 1:页面出现 404,但文件明明存在
大概率是 root 与 alias 用混了。root 会把 location 的 URI 拼到根目录后面,alias 则直接用别名替换前缀:
# root:访问 /images/a.png → /var/www/static/images/a.png
location /images/ {
root /var/www/static;
}
# alias:访问 /images/a.png → /var/www/static/a.png
location /images/ {
alias /var/www/static/;
}
排查命令:先确认实际磁盘路径,再用 nginx -t 校验语法,最后看 error.log:
grep -E "open\(\).*failed|No such file" /var/log/nginx/error.log | tail -20
问题 2:无限 301 重定向(ERR_TOO_MANY_REDIRECTS)
通常是 rewrite 规则把 / 也重写了,形成 A→B→A 的循环。先在 curl 里看响应头:
curl -sI http://example.com/ | grep -i location curl -sI https://example.com/ | grep -i location
再检查是否有两条相互冲突的规则(如同时配置了 http 跳 https 和 https 跳 http)。一个常见诱因是 CDN 或反代回源到 80 端口,导致回源请求也被强制跳转,正确做法是在反代层只信任特定来源:
# 只在非回源场景强制跳转
map $http_x_forwarded_proto $scheme_ok {
default 0;
https 1;
}
server {
listen 80;
if ($scheme_ok = 0) {
return 301 https://$host$request_uri;
}
}
问题 3:rewrite 之后静态资源 404
检查标记是 last 还是 break,并确认新 URI 在目标 location 里有对应的 root/alias。开启调试日志能直接看到内部跳转过程:
# 临时在 http 块中开启 error_log /var/log/nginx/error.log debug;
然后重新请求,观察日志中的 internal redirect 与 rewritten data 字段,能清晰看到每一条 rewrite 的执行轨迹。
问题 4:正则 location 意外拦截了动态请求
正则一旦命中就覆盖所有前缀匹配(除非 ^~)。如果 PHP 文件被静态规则吞掉,加一条更靠前的精确正则:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
}
正则按书写顺序匹配,把更具体的规则写在前面即可。
七、性能与安全建议
- 能用前缀
location就别用正则,正则越多 CPU 开销越大。 - 根路径
location = /做精确匹配,避免location /兜底误伤。 - 对外跳转使用
permanent/redirect,内部路由用last/break,职责要分清。 - rewrite 尽量写在 server 级,减少 location 内的重复匹配。
- 生产环境上线前先
nginx -t校验并nginx -s reload平滑加载。
nginx -t && nginx -s reload
八、总结
location 与 rewrite 是 Nginx 配置里最容易出错也最值得掌握的部分。记住四句话:精确匹配优先、最长前缀兜底、正则按序命中、^~ 阻断正则;rewrite 用 last 重跑匹配、break 就地执行、redirect 外跳、permanent 永跳。遇到问题时先用 curl -I 看响应头,再开 debug 日志跟内部跳转轨迹,最后用 nginx -t 验证语法,绝大多数疑难杂症都能快速定位。


