Apache mod_rewrite 实现复杂URL重写的核心是三件事:确保模块启用且AllowOverride All、RewriteRule匹配URI path时开头不加/、严格区分[R]重定向与无R内部重写。

Apache mod_rewrite 实现复杂 URL 重写,核心不在正则多炫酷,而在三件事做对:模块启用且允许覆盖、规则匹配逻辑清晰、重写与重定向语义不混淆。下面直接讲实战中真正起作用的要点。
第一步:确保 rewrite 能生效(90%问题卡在这)
规则写得再准,没启用或被屏蔽就全白搭:
- 运行 a2enmod rewrite(Debian/Ubuntu),或检查 httpd.conf 中
LoadModule rewrite_module modules/mod_rewrite.so是否已取消注释 - 对应网站目录必须配置 AllowOverride All(不能是 None 或 FileInfo 不足);若用虚拟主机,需在
<VirtualHost>内明确设置 - .htaccess 文件必须放在请求路径对应的物理目录下,且 Apache 进程(如 www-data)有读取权限
- 每段规则开头都得写 RewriteEngine On —— 它不继承,也不全局生效
第二步:正则匹配只看 URI path,且开头不带 /
这是最常写错的地方。RewriteRule 默认只匹配路径部分(不含 ? 后的 query string),而且在 .htaccess 中,Apache 会自动剥离当前目录前缀:
Apache Superset 是一个广泛采用的开源 BI 平台,用于 SQL 探索、图表构建和仪表板交付。当代理需要查询仓库数据、组装仪表板或使用成熟的分析界面解释指标而不是临时笔记本代码时,此技能非常有用。
- 网站根目录下
.htaccess中写^article/(\d+)$→ 匹配/article/123 - 但写成
^/article/(\d+)$→ 永远不匹配(开头的 / 被自动去掉) - 子目录
/blog/.htaccess中,^post/(\d+)$匹配的是/blog/post/123的post/123部分 - 要处理查询参数(如 ?ref=abc),必须用 RewriteCond %{QUERY_STRING} 单独判断
第三步:区分重定向(R)和内部重写(无 R),避免循环
浏览器地址栏变不变,背后机制完全不同:
- 加 [R=302] 或 [R=301,L] → 浏览器收到跳转响应,地址栏更新,多一次 HTTP 请求
- 不加 [R] → 请求仍在服务器内部转发,用户无感知,零网络开销,适合前端控制器模式(如所有请求进 index.php)
- 混合使用时注意顺序:比如先强制 HTTPS,再统一去 www,最后路由到 PHP 入口;中间漏掉 [L] 可能导致后续规则意外触发
- 测试阶段一律用 [R=302],避免 301 被浏览器缓存导致调试失效
第四步:几个高频实战写法(可直接套用)
以下规则均假设放在网站根目录 .htaccess 中,已启用 RewriteEngine:
-
隐藏 .php 后缀:
RewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME}.php -fRewriteRule ^(.+)$ $1.php [L] -
统一入口(Laravel/ThinkPHP 类型):
RewriteCond %{REQUEST_FILENAME} !-fRewriteCond %{REQUEST_FILENAME} !-dRewriteRule ^(.*)$ index.php/$1 [L](支持 PATH_INFO) -
SEO 友好伪静态(/news/123 → /article.php?id=123):
RewriteRule ^news/(\d+)$ article.php?id=$1 [L,QSA](QSA 保留原始 ?utm_source=xxx 等参数) -
强制 HTTPS + www(分两步,防循环):
RewriteCond %{HTTPS} off [OR]RewriteCond %{HTTP_HOST} !^www\. [NC]RewriteCond %{ENV:REDIRECT_STATUS} ^$RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

















