📝本文首发于 栏轩·阁
欢迎访问阅读原文,获取更好的阅读体验。
一、URL Rewrite 是什么?为什么需要它?
URL Rewrite(URL 重写)是 Nginx 的核心功能之一,它允许你在请求到达服务器时,动态地修改请求的 URL,而不改变用户浏览器地址栏中显示的链接(或者根据需要改变)。
核心价值
- SEO 优化:将
?page=2&id=123这样的动态参数 URL 转换为/page/2/id/123.html的静态化 URL,搜索引擎更喜欢后者 - 用户体验:简洁、有语义的 URL 更容易被用户理解和记忆
- 安全:隐藏真实的请求参数和目录结构,避免暴露技术细节
- 兼容性:网站改版时,将旧链接 301 重定向到新链接,保证 SEO 权重不丢失
二、一个典型场景:动态页面伪装成静态 HTML
需求
原始页面:/good?page=2
希望用户访问:/good/2.html就能看到同样的内容
这样做的好处:
- 隐藏真实参数:不暴露
?page=这样的查询字符串 - 伪装成静态 HTML:搜索引擎和用户都更偏好静态化的 URL
- 更利于 SEO:搜索引擎对静态 URL 的索引和排名更友好
完整配置实现
server { listen 80; server_name example.com; # 用户访问 /good/2.html 时,内部重写为 /good?page=2 location / { rewrite ^/good/(\d+)\.html$ /good?page=$1 last; } # 实际处理请求的 location location /good { # 这里是正常的业务逻辑 proxy_pass http://backend; # 或者 try_files 等 } }配置逐行解析
| 配置 | 说明 |
|---|---|
rewrite | Nginx 重写指令 |
^/good/(\d+)\.html$ | 正则表达式:匹配类似/good/2.html的链接,(\d+)捕获数字部分 |
/good?page=$1 | 替换后的 URL:$1引用第一个捕获的分组(即数字 2) |
last | flag 标记:表示完成重写后,用新 URL 重新匹配 location |
三、Rewrite 统一语法
基本语法
rewrite regex replacement [flag];| 部分 | 说明 |
|---|---|
regex | 正则表达式,匹配用户请求的 URI |
replacement | 替换后的 URI,可以引用正则捕获的变量($1、$2等) |
flag | 可选,控制重写后的行为方式 |
Rewrite 指令的作用位置
rewrite 可以出现在以下两个上下文中:
server块— 在 server 级别对所有请求生效location块— 仅对匹配该 location 的请求生效if块— 结合条件判断使用
# 在 server 块中全局生效 server { rewrite ^/old-path/(.*)$ /new-path/$1 permanent; } # 在 location 块中对特定路径生效 location /images { rewrite ^/images/(.*)$ /assets/$1 break; }四、四大 Flag 标记详解
Flag 是 rewrite 指令的关键部分,决定了重写后 Nginx 的行为方式。
| Flag | 行为 | 适用场景 | 是否改变浏览器 URL |
|---|---|---|---|
last | 重写后重新搜索location 匹配 | 在 location 块中使用,需要重新匹配时 | ❌ 内部重写 |
break | 重写后停止rewrite 模块的处理,但不重新匹配 location | 当前 location 已经匹配正确,只需重写 URI 即可 | ❌ 内部重写 |
redirect | 返回302 临时重定向 | 临时性的 URL 变更 | ✅ 浏览器地址变化 |
permanent | 返回301 永久重定向 | 永久性的 URL 变更、网站改版 | ✅ 浏览器地址变化 |
如何选择?一张图看懂
用户请求 URL │ ▼ rewrite 匹配成功 │ ├── flag = last → 用新 URI 重新匹配 location(内部跳转,URL 不变) ├── flag = break → 用新 URI 继续当前请求(不重新匹配,URL 不变) ├── flag = redirect → 返回 302,告诉浏览器换地址(URL 会变) └── flag = permanent → 返回 301,永久迁移(URL 会变,搜索引擎更新索引)示例:last vs break 的区别
location /a/ { rewrite ^/a/(.*)$ /b/$1 last; } location /b/ { rewrite ^/b/(.*)$ /c/$1 last; }请求/a/foo→ 第一次 rewrite 为/b/foo,last触发重新匹配 → 进入location /b/→ 第二次 rewrite 为/c/foo
如果用break替换上面的last:
- 请求
/a/foo→ rewrite 为/b/foo,但break不重新匹配 → 直接处理/b/foo,不会执行location /b/中的 rewrite
301 vs 302 的选择
| 场景 | 推荐 |
|---|---|
| 网站永久迁移到新域名 | permanent(301) |
| 旧链接永久更换为新格式 | permanent(301) |
| 临时维护页面 | redirect(302) |
| A/B 测试流量分发 | redirect(302) |
| 不确定是否永久改动 | redirect(302) |
六、实战案例详解
案例 1:文章 ID 美化
需求:/article.php?id=123→/article/123.html
location / { rewrite ^/article/(\d+)\.html$ /article.php?id=$1 last; }案例 2:多参数传递
需求:/list?cat=tech&page=2→/list/tech/2.html
location / { rewrite ^/list/(\w+)/(\d+)\.html$ /list?cat=$1&page=$2 last; }案例 3:分类+文章名(WordPress 风格)
需求:/post?slug=hello-world→/2024/01/hello-world.html
location / { rewrite ^/(\d{4})/(\d{2})/([\w-]+)\.html$ /post?year=$1&month=$2&slug=$3 last; }案例 4:域名跳转(301 永久重定向)
需求:旧域名old-site.com全部跳转到new-site.com
server { listen 80; server_name old-site.com www.old-site.com; rewrite ^(.*)$ http://new-site.com$1 permanent; } # 更推荐用 return 301,性能更好: server { listen 80; server_name old-site.com www.old-site.com; return 301 http://new-site.com$request_uri; }案例 5:强制 HTTPS
server { listen 80; server_name example.com; rewrite ^(.*)$ https://$host$1 permanent; # 或用 return(更高效): # return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; # 正常的 HTTPS 配置 }五、常见问题与注意事项
1. Rewrite 与 Return 的选择
return比rewrite性能更好,因为它直接中断请求处理,不经过正则匹配。能用return的场景优先用return:
# ✅ 推荐:return 301 return 301 https://$host$request_uri; # ❌ 不推荐:rewrite ... permanent rewrite ^(.*)$ https://$host$1 permanent;2. 注意死循环
# ❌ 错误示例:会陷入无限循环 location / { rewrite ^/article/(\d+)$ /article/$1 last; }因为 rewrite 后重新匹配 location,又匹配到/这个 location,再次触发 rewrite。
解决方法:使用break或将具体规则放在更精确的 location 中。
3. Proxy_Pass 下的 Rewrite 顺序
当同时使用 proxy_pass 和 rewrite 时,rewrite 在 proxy_pass 之前执行。可以先在 location 中 rewrite 修改 URI,再 proxy_pass 转发。
举个例子,有个后端接口接收/api/user?id=123,但你想让用户访问/user/123就能调用它:
location / { rewrite ^/user/(\d+)$ /api/user?id=$1 break; proxy_pass http://backend_server; }执行流程:
- 用户访问
/user/123 rewrite先把 URI 改为/api/user?id=123- 然后
proxy_pass将修改后的请求转发给后端
再看一个实际场景:前端静态资源和后端 API 混用:
server { listen 80; server_name blog.example.com; location / { # 先尝试读取静态文件,没有命中才 rewrite root /var/www/static; } location /api/ { # 把 /api/article/42 重写为 /index.php?route=article&id=42 rewrite ^/api/(\w+)/(\d+)$ /index.php?route=$1&id=$2 break; proxy_pass http://php_backend:9000; } }访问/api/article/42时:
rewrite先将路径重写为/index.php?route=article&id=42- 然后
proxy_pass将重写后的请求转发给 PHP 后端处理
4. 调试 Rewrite 规则
启用 rewrite 日志,方便调试:
server { # 开启 rewrite 日志 rewrite_log on; error_log /var/log/nginx/rewrite_error.log notice; }在错误日志中可以看到每个 rewrite 的执行步骤,非常有助于排查问题。
六、总结
| 关键点 | 要点 |
|---|---|
| 语法 | rewrite regex replacement [flag] |
| 四个 flag | last(重匹配)、break(停止)、redirect(302)、permanent(301) |
| 正则捕获 | ()捕获,$1、$2引用 |
| 性能 | 单纯的重定向用return而不是rewrite |
| 调试 | 开启rewrite_log on查看重写过程 |
| SEO | 301 传递权重,302 不传递,按场景选择 |
URL Rewrite 是 Nginx 配置中最常用也最强大的功能之一,掌握了它,你就能随心所欲地控制网站的 URL 结构,让链接既美观又对搜索引擎友好。