mod_rewrite 详解
mod_rewrite 详解
mod_rewrite 是 Apache 中最强大、最灵活的模块之一,提供基于规则的 URL 重写引擎。
1. 基础概念
1.1 工作原理
请求 URL → 匹配规则 → 应用条件 → 执行重写 → 输出 URL
mod_rewrite 在请求处理的不同阶段都可以工作:
| 阶段 | 说明 | 使用场景 |
|---|---|---|
per-server (VirtualHost) | 服务器配置阶段 | 虚拟主机级别重写 |
per-dir (.htaccess) | 目录配置阶段 | .htaccess 中的规则 |
1.2 启用 mod_rewrite
# Debian/Ubuntu
sudo a2enmod rewrite
sudo systemctl reload apache2
# CentOS/RHEL
# 确保 httpd.conf 中包含:
# LoadModule rewrite_module modules/mod_rewrite.so
1.3 基本语法
# 启用重写引擎
RewriteEngine On
# 基本重写规则
RewriteRule 模式 替换 [标志]
# 带条件的重写
RewriteCond 测试字符串 条件模式 [标志]
RewriteRule 模式 替换 [标志]
2. RewriteRule 详解
2.1 语法
RewriteRule Pattern Substitution [FLAGS]
模式(Pattern):正则表达式,匹配请求的 URL 路径(不含查询字符串)
替换(Substitution):目标 URL 或文件路径
标志(Flags):控制重写行为
2.2 基本示例
# 简单重定向
RewriteRule ^old-page\.html$ /new-page.html [R=301,L]
# URL 路径重写(内部)
RewriteRule ^products/([0-9]+)$ /product.php?id=$1 [L]
# 多参数传递
RewriteRule ^category/([a-z]+)/page/([0-9]+)$ /list.php?cat=$1&page=$2 [L,QSA]
# 条件重写 - 仅当文件不存在时
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [L,QSA]
2.3 正则表达式常用语法
| 符号 | 含义 | 示例 |
|---|---|---|
^ | 开始 | ^/api/ 匹配以 /api/ 开头 |
$ | 结束 | \.html$ 匹配以 .html 结尾 |
. | 任意字符 | a.c 匹配 abc, adc |
* | 零或多次 | ab*c 匹配 ac, abc, abbc |
+ | 一或多次 | ab+c 匹配 abc, abbc |
? | 零或一次 | ab?c 匹配 ac, abc |
() | 捕获组 | (.*) 捕获整个路径 |
[] | 字符类 | [a-z] 小写字母 |
[^] | 否定字符类 | [^/]+ 非斜杠字符 |
\d | 数字 | [0-9] |
\w | 单词字符 | [a-zA-Z0-9_] |
\s | 空白 | 空格、制表符等 |
2.4 反向引用
# $N - 第 N 个捕获组 (1-9)
RewriteRule ^user/([a-z]+)/profile$ /users.php?name=$1 [L]
# 多个捕获组
RewriteRule ^(\d{4})/(\d{2})/(\d{2})/(.+)$ /post.php?year=$1&month=$2&day=$3&slug=$4 [L]
# %{N} - 在 RewriteCond 中的反向引用
RewriteCond %{HTTP_HOST} ^(.+)\.example\.com$
RewriteRule ^(.*)$ /sites/%1/$1 [L]
3. RewriteCond 详解
3.1 语法
RewriteCond TestString CondPattern [FLAGS]
3.2 测试字符串变量
| 变量 | 含义 |
|---|---|
%{HTTP_HOST} | 请求的主机名 |
%{REQUEST_URI} | 请求的 URI |
%{REQUEST_FILENAME} | 请求的文件路径 |
%{QUERY_STRING} | 查询字符串 |
%{HTTP_REFERER} | 来源页面 |
%{HTTP_USER_AGENT} | 用户代理 |
%{REMOTE_ADDR} | 客户端 IP |
%{HTTPS} | 是否 HTTPS |
%{SERVER_PORT} | 服务器端口 |
%{TIME_YEAR} | 当前年份 |
%{TIME_MON} | 当前月份 |
%{TIME_DAY} | 当前日期 |
3.3 条件模式
# 正则匹配
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
# 词法比较
RewriteCond %{HTTP_HOST} =example.com
# 整数比较
RewriteCond %{SERVER_PORT} >8080
RewriteCond %{SERVER_PORT} <9000
# 文件测试
RewriteCond %{REQUEST_FILENAME} -f # 文件存在
RewriteCond %{REQUEST_FILENAME} !-f # 文件不存在
RewriteCond %{REQUEST_FILENAME} -d # 目录存在
RewriteCond %{REQUEST_FILENAME} !-d # 目录不存在
RewriteCond %{REQUEST_FILENAME} -l # 符号链接
RewriteCond %{REQUEST_FILENAME} -s # 文件有大小(非空)
# 环境变量
RewriteCond %{ENV:REDIRECT_STATUS} ^$
3.4 条件标志
| 标志 | 含义 |
|---|---|
[NC] | 不区分大小写 |
[OR] | 逻辑或(默认为逻辑与) |
[NC,OR] | 组合 |
3.5 条件组合示例
# AND 逻辑(默认)
RewriteCond %{HTTPS} off
RewriteCond %{HTTP_HOST} ^www\. [NC]
# 两个条件都满足才执行
# OR 逻辑
RewriteCond %{HTTP_HOST} ^example\.com$ [NC,OR]
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
# 任一条件满足即执行
# 复杂组合
RewriteCond %{REQUEST_URI} !^/admin [NC,OR]
RewriteCond %{REMOTE_ADDR} ^192\.168\.1\.
RewriteCond %{REQUEST_FILENAME} !-f
# (不是 admin 路径 OR IP 在内网) AND 文件不存在
4. 标志(Flags)详解
4.1 常用标志
| 标志 | 含义 | 说明 |
|---|---|---|
[L] | Last | 停止处理后续规则 |
[R=301] | Redirect | 永久重定向 |
[R=302] | Redirect | 临时重定向(默认) |
[F] | Forbidden | 返回 403 禁止 |
[G] | Gone | 返回 410 已删除 |
[NC] | No Case | 不区分大小写 |
[QSA] | Query String Append | 追加查询字符串 |
[QSD] | Query String Discard | 删除查询字符串 |
[PT] | Pass Through | 传递给下一个处理器 |
[NE] | No Escape | 不转义特殊字符 |
[NS] | No Subrequest | 不匹配子请求 |
[END] | End | 终止所有重写(比 L 更强) |
4.2 重定向标志
# 301 永久重定向(SEO 友好)
RewriteRule ^old-page\.html$ /new-page.html [R=301,L]
# 302 临时重定向
RewriteRule ^maintenance\.html$ /coming-soon.html [R=302,L]
# 303 See Other
RewriteRule ^submit$ /thank-you.html [R=303,L]
# 307 Temporary Redirect(保持请求方法)
RewriteRule ^temp$ /new-location [R=307,L]
# 308 Permanent Redirect(保持请求方法)
RewriteRule ^permanent$ /new-location [R=308,L]
4.3 查询字符串处理
# QSA - 追加查询字符串
RewriteRule ^search/(.+)$ /search.php?q=$1 [L,QSA]
# /search/apache?page=2 → /search.php?q=apache&page=2
# QSD - 删除查询字符串(Apache 2.4+)
RewriteRule ^page$ /new-page [L,QSD]
# /page?old=param → /new-page
# QSA + QSD 组合
RewriteRule ^page$ /new-page?new=param [L,QSA,QSD]
# /page?old=param → /new-page?new=param
4.4 环境变量标志
# 设置环境变量
RewriteRule ^(.+\.php)$ $1 [E=PHP_SCRIPT:1]
# 基于环境变量的条件
RewriteCond %{ENV:PHP_SCRIPT} 1
RewriteRule ^(.*)$ /handler.php [L]
# REDIRECT_ 前缀
# 重写后,环境变量会加上 REDIRECT_ 前缀
# 如 E=FOO:bar → REDIRECT_FOO=bar
5. 常见用法与示例
5.1 SEO 相关
# 强制 HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# 移除 www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# 添加 www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]
# 移除尾部斜杠
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]
# 添加尾部斜杠(目录请求)
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ $1/ [R=301,L]
# 移除 .html 扩展名
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^\.]+)$ $1.html [NC,L]
# 添加 .html 扩展名
RewriteCond %{REQUEST_URI} !\.[a-zA-Z0-9]{1,5}$
RewriteRule ^([^/]+)/?$ $1.html [L]
5.2 URL 路由
# MVC 路由
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?route=$1 [QSA,L]
# RESTful API 路由
# /api/users/123 → /api/users.php?id=123
RewriteRule ^api/users/([0-9]+)$ /api/users.php?id=$1 [L]
RewriteRule ^api/users$ /api/users.php [L]
RewriteRule ^api/posts/([0-9]+)$ /api/posts.php?id=$1 [L]
# 多语言 URL
# /en/about → /pages/about.php?lang=en
# /zh/about → /pages/about.php?lang=zh
RewriteRule ^(en|zh)/(.+)$ /pages/$2.php?lang=$1 [L,QSA]
5.3 安全防护
# 防止目录遍历
RewriteCond %{REQUEST_URI} \.\./ [OR]
RewriteCond %{REQUEST_URI} \.\.\\
RewriteRule .* - [F]
# 阻止特定 User-Agent
RewriteCond %{HTTP_USER_AGENT} (crawl|bot|spider|scraper) [NC]
RewriteRule .* - [F]
# 阻止空 User-Agent
RewriteCond %{HTTP_USER_AGENT} ^-?$
RewriteRule .* - [F]
# 保护敏感文件
RewriteRule \.(env|git|htaccess|htpasswd|ini|log|sh|sql|bak)$ - [F,NC]
# 防止图片盗链
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https://(www\.)?example\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif|bmp|webp)$ - [F,NC]
# 限制文件上传目录执行 PHP
RewriteCond %{REQUEST_URI} /uploads/ [NC]
RewriteRule \.php$ - [F]
5.4 重定向管理
# 域名迁移
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?old-domain\.com$ [NC]
RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L]
# 子域名到子目录
RewriteCond %{HTTP_HOST} ^blog\.example\.com$ [NC]
RewriteRule ^(.*)$ /blog/$1 [L]
# 旧 URL 批量重定向
RedirectMatch 301 ^/old-category/(.*)$ /new-category/$1
RedirectMatch 301 ^/products\.php\?id=([0-9]+)$ /products/$1
# 带查询参数的重定向
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^product\.php$ /products/%1? [R=301,L]
5.5 性能优化
# 早期退出 - 将常用规则放前面
RewriteRule ^css/(.*)$ /css/$1 [L]
RewriteRule ^js/(.*)$ /js/$1 [L]
RewriteRule ^images/(.*)$ /images/$1 [L]
# 跳过静态文件
RewriteCond %{REQUEST_URI} \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ [NC]
RewriteRule .* - [L]
# 使用 RewriteMap 提高性能
# httpd.conf 中定义
RewriteMap lc int:tolower
RewriteRule ^(.*)$ ${lc:$1} [L]
6. RewriteMap 高级用法
6.1 映射类型
# 内部函数映射
RewriteMap lc int:tolower
RewriteMap uc int:toupper
RewriteMap escape int:escape
# 文本文件映射
RewriteMap redirects txt:/etc/apache2/redirects.txt
# 随机映射
RewriteMap servers rnd:/etc/apache2/servers.txt
# DBM 映射(高性能)
RewriteMap redirects dbm:/etc/apache2/redirects.dbm
# 外部程序映射
RewriteMap lookup prg:/etc/apache2/lookup.pl
6.2 映射文件格式
# /etc/apache2/redirects.txt
# 格式: key value
old-page.html /new-page.html
old-about.html /about
/contact /contact-us
/blog/post-1 /blog/new-post-1
# /etc/apache2/servers.txt
# 随机选择后端
backend server1.example.com|server2.example.com|server3.example.com
6.3 使用 RewriteMap
# 使用文本映射
RewriteMap redirects txt:/etc/apache2/redirects.txt
RewriteRule ^(.+)$ ${redirects:$1} [R=301,L]
# 使用大小写转换
RewriteMap lc int:tolower
RewriteRule ^(.*)$ ${lc:$1} [L]
# 使用随机后端
RewriteMap servers rnd:/etc/apache2/servers.txt
RewriteRule ^(.*)$ http://${servers:backend}/$1 [P,L]
7. 调试与故障排除
7.1 启用日志
# httpd.conf
LogLevel alert rewrite:trace3
# 日志级别:
# trace1 - 基本信息
# trace2 - 详细信息
# trace3 - 非常详细
# trace4 - 调试信息
# trace5 - 极其详细
# trace6 - 最详细
# trace7 - 超详细
# trace8 - 全部信息
7.2 常见问题
# 问题:规则不生效
# 解决:检查 RewriteEngine 是否启用
RewriteEngine On
# 问题:死循环
# 解决:使用 [L] 标志和条件检查
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.*)$ index.php?url=$1 [L]
# 问题:.htaccess 不生效
# 解决:检查 AllowOverride
<Directory "/var/www/html">
AllowOverride All
</Directory>
# 问题:特殊字符转义
# 解决:使用 [NE] 标志
RewriteRule ^search/(.+)$ /search.php?q=$1 [NE,L]
7.3 测试工具
# 在线测试
# https://htaccess.madewithlove.com/
# https://regex101.com/
# 命令行测试
curl -I -L http://localhost/old-page.html
curl -v http://localhost/test 2>&1 | grep -i location
# Apache 配置检查
apachectl configtest
apachectl -t -D DUMP_MODULES | grep rewrite
8. 注意事项
- 性能影响:每个请求都会经过所有 RewriteRule,规则越多性能影响越大
- 规则顺序:规则按顺序执行,先匹配的先执行
- 301 vs 302:永久迁移用 301,临时用 302,SEO 效果不同
- 测试环境:在测试环境充分测试后再部署到生产
- 备份规则:重写规则复杂,修改前务必备份
9. 扩展阅读
10. 总结
mod_rewrite 是 Apache 中最强大的模块:
- 灵活的 URL 重写:支持复杂的正则表达式匹配
- 条件逻辑:RewriteCond 提供丰富的条件判断
- 多种标志:精确控制重写行为
- 广泛应用:SEO、安全、路由、性能优化
掌握 mod_rewrite 是 Apache 管理员的必备技能。