Apache URL 重写 #
mod_rewrite 概述 #
什么是 URL 重写? #
text
┌─────────────────────────────────────────────────────────────┐
│ URL 重写概念 │
├─────────────────────────────────────────────────────────────┤
│ │
│ URL 重写:将一个 URL 转换为另一个 URL │
│ │
│ 用户访问: │
│ https://example.com/article/123 │
│ │
│ 实际处理: │
│ https://example.com/article.php?id=123 │
│ │
│ 作用: │
│ ├── 美化 URL(SEO 友好) │
│ ├── 隐藏实现细节 │
│ ├── 网站迁移和重定向 │
│ ├── 访问控制 │
│ └── 负载均衡 │
│ │
└─────────────────────────────────────────────────────────────┘
启用 mod_rewrite #
bash
# Ubuntu/Debian
sudo a2enmod rewrite
sudo systemctl restart apache2
# CentOS/RHEL
# 在配置文件中取消注释
# LoadModule rewrite_module modules/mod_rewrite.so
sudo systemctl restart httpd
基本配置 #
apache
# ============================================
# 启用 URL 重写
# ============================================
# 在虚拟主机或目录配置中
<Directory /var/www/html>
Options -Indexes +FollowSymLinks
AllowOverride All
# AllowOverride All 允许 .htaccess 中的重写规则
</Directory>
# 启用重写引擎
RewriteEngine On
# 设置重写基准路径
RewriteBase /
RewriteRule 基础 #
基本语法 #
text
┌─────────────────────────────────────────────────────────────┐
│ RewriteRule 语法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ RewriteRule Pattern Substitution [Flags] │
│ │
│ Pattern: 匹配模式(正则表达式) │
│ Substitution: 替换目标 │
│ Flags: 标志(可选) │
│ │
│ 示例: │
│ RewriteRule ^old-page$ /new-page [R=301,L] │
│ │
└─────────────────────────────────────────────────────────────┘
简单重写示例 #
apache
# ============================================
# 简单重写示例
# ============================================
RewriteEngine On
# 简单重定向
RewriteRule ^old-page$ /new-page [R=301,L]
# 带参数重写
RewriteRule ^article/([0-9]+)$ /article.php?id=$1 [L]
# 多个参数
RewriteRule ^category/([a-z]+)/([0-9]+)$ /category.php?name=$1&page=$2 [L]
# 隐藏 .php 扩展名
RewriteRule ^about$ /about.php [L]
# 首页重写
RewriteRule ^$ /index.php [L]
正则表达式 #
text
┌─────────────────────────────────────────────────────────────┐
│ 正则表达式语法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 字符匹配: │
│ . 任意单个字符 │
│ [abc] a、b 或 c │
│ [^abc] 非 a、b、c │
│ [0-9] 数字 0-9 │
│ [a-z] 小写字母 │
│ [A-Z] 大写字母 │
│ │
│ 量词: │
│ * 0 次或多次 │
│ + 1 次或多次 │
│ ? 0 次或 1 次 │
│ {n} 恰好 n 次 │
│ {n,} 至少 n 次 │
│ {n,m} n 到 m 次 │
│ │
│ 定位符: │
│ ^ 字符串开头 │
│ $ 字符串结尾 │
│ \b 单词边界 │
│ │
│ 特殊字符: │
│ \d 数字 │
│ \w 单词字符 │
│ \s 空白字符 │
│ () 分组捕获 │
│ │
└─────────────────────────────────────────────────────────────┘
捕获组引用 #
apache
# ============================================
# 捕获组引用
# ============================================
RewriteEngine On
# 单个捕获组
# 请求: /article/123
# 结果: /article.php?id=123
RewriteRule ^article/([0-9]+)$ /article.php?id=$1 [L]
# 多个捕获组
# 请求: /category/tech/page/2
# 结果: /category.php?cat=tech&p=2
RewriteRule ^category/([a-z]+)/page/([0-9]+)$ /category.php?cat=$1&p=$2 [L]
# 命名捕获组
RewriteRule ^user/(?<id>[0-9]+)$ /user.php?id=%{id} [L]
# 非捕获组
RewriteRule ^(?:api|v1)/users$ /users.php [L]
RewriteCond 条件 #
基本语法 #
text
┌─────────────────────────────────────────────────────────────┐
│ RewriteCond 语法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ RewriteCond TestString CondPattern [Flags] │
│ │
│ TestString: 测试字符串(可以是变量) │
│ CondPattern: 条件模式 │
│ Flags: 标志(可选) │
│ │
│ 多个 RewriteCond 默认是 AND 关系 │
│ 使用 [OR] 标志可以变为 OR 关系 │
│ │
└─────────────────────────────────────────────────────────────┘
服务器变量 #
apache
# ============================================
# 常用服务器变量
# ============================================
# HTTP 头信息
%{HTTP_HOST} # 主机名
%{HTTP_USER_AGENT} # 用户代理
%{HTTP_REFERER} # 来源页面
%{HTTP_COOKIE} # Cookie
%{HTTP_ACCEPT} # Accept 头
# 请求信息
%{REQUEST_URI} # 请求 URI
%{REQUEST_METHOD} # 请求方法
%{QUERY_STRING} # 查询字符串
%{REMOTE_ADDR} # 客户端 IP
%{REMOTE_HOST} # 客户端主机名
# 服务器信息
%{SERVER_NAME} # 服务器名
%{SERVER_PORT} # 服务器端口
%{SERVER_PROTOCOL} # 协议版本
%{DOCUMENT_ROOT} # 文档根目录
# 时间信息
%{TIME_YEAR} # 年
%{TIME_MON} # 月
%{TIME_DAY} # 日
%{TIME_HOUR} # 时
%{TIME_MIN} # 分
%{TIME_SEC} # 秒
# 特殊变量
%{HTTPS} # 是否 HTTPS(on/off)
%{REQUEST_FILENAME} # 请求文件路径
条件示例 #
apache
# ============================================
# RewriteCond 示例
# ============================================
RewriteEngine On
# 检查是否 HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 检查域名
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]
# 检查文件是否存在
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php?url=$1 [L]
# 检查 IP 地址
RewriteCond %{REMOTE_ADDR} ^192\.168\.1\.
RewriteRule ^admin - [F]
# 检查用户代理
RewriteCond %{HTTP_USER_AGENT} (bot|crawl|spider) [NC]
RewriteRule ^ - [F]
# 多条件(AND)
RewriteCond %{HTTPS} off
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301]
# 多条件(OR)
RewriteCond %{HTTP_HOST} ^example\.com$ [NC,OR]
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [L,R=301]
条件模式 #
text
┌─────────────────────────────────────────────────────────────┐
│ 条件模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 字符串比较: │
│ Pattern 等于 │
│ !Pattern 不等于 │
│ <Pattern 小于(字典序) │
│ >Pattern 大于(字典序) │
│ =Pattern 精确等于 │
│ │
│ 正则表达式: │
│ Pattern 匹配 │
│ !Pattern 不匹配 │
│ │
│ 文件测试: │
│ -f 文件存在 │
│ !-f 文件不存在 │
│ -d 目录存在 │
│ !-d 目录不存在 │
│ -s 文件存在且大小 > 0 │
│ -l 符号链接 │
│ -x 可执行文件 │
│ -F 文件存在(通过子请求检查) │
│ -U URL 存在(通过子请求检查) │
│ │
└─────────────────────────────────────────────────────────────┘
重写标志 #
常用标志 #
text
┌─────────────────────────────────────────────────────────────┐
│ RewriteRule 标志 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [R=code] 重定向 │
│ 301: 永久重定向 │
│ 302: 临时重定向(默认) │
│ 307: 临时重定向(保持方法) │
│ 308: 永久重定向(保持方法) │
│ │
│ [L] 最后一条规则(停止处理) │
│ │
│ [NC] 不区分大小写 │
│ │
│ [QSA] 追加查询字符串 │
│ │
│ [PT] 传递给其他模块处理 │
│ │
│ [F] 禁止访问(返回 403) │
│ │
│ [G] 已删除(返回 410) │
│ │
│ [P] 代理请求 │
│ │
│ [S=n] 跳过接下来的 n 条规则 │
│ │
│ [C] 与下一条规则链接 │
│ │
│ [CO=name] 设置 Cookie │
│ │
│ [E=var] 设置环境变量 │
│ │
│ [T=type] 设置 MIME 类型 │
│ │
│ [NE] 不转义特殊字符 │
│ │
│ [NS] 不处理子请求 │
│ │
└─────────────────────────────────────────────────────────────┘
标志示例 #
apache
# ============================================
# 标志使用示例
# ============================================
RewriteEngine On
# [R=301] 永久重定向
RewriteRule ^old-page$ /new-page [R=301,L]
# [NC] 不区分大小写
RewriteRule ^about$ /about.php [NC,L]
# [QSA] 保留查询字符串
# 请求: /page?sort=date
# 结果: /index.php?url=page&sort=date
RewriteRule ^([a-z]+)$ /index.php?url=$1 [QSA,L]
# [F] 禁止访问
RewriteRule ^secret - [F]
# [G] 资源已删除
RewriteRule ^old-resource - [G]
# [P] 代理请求
RewriteRule ^api/(.*)$ http://localhost:3000/$1 [P,L]
# [E] 设置环境变量
RewriteRule ^admin - [E=ADMIN:1,L]
# [CO] 设置 Cookie
RewriteRule ^ - [CO=visited:yes:example.com:1440:/]
# [T] 设置 MIME 类型
RewriteRule \.json$ - [T=application/json]
# [NE] 不转义
RewriteRule ^search/(.*)$ /search.php?q=$1 [NE,L]
# 组合标志
RewriteRule ^old/(.*)$ /new/$1 [R=301,NC,L]
常见应用场景 #
强制 HTTPS #
apache
# ============================================
# 强制 HTTPS
# ============================================
RewriteEngine On
# 方法 1:简单方式
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 方法 2:带 www
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301]
# 方法 3:使用 %{SERVER_PORT}
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# 方法 4:X-Forwarded-Proto(反向代理后)
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
域名规范化 #
apache
# ============================================
# 域名规范化
# ============================================
RewriteEngine On
# www 转非 www
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]
# 非 www 转 www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^([a-z.]+)$ [NC]
RewriteRule ^(.*)$ https://www.%1/$1 [L,R=301]
# 多域名重定向到一个域名
RewriteCond %{HTTP_HOST} ^(old1\.com|old2\.com|old3\.com)$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [L,R=301]
美化 URL #
apache
# ============================================
# URL 美化
# ============================================
RewriteEngine On
# 隐藏 .php 扩展名
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.*)$ $1.php [L]
# 隐藏 .html 扩展名
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.*)$ $1.html [L]
# 文章 URL
# /article/123 -> /article.php?id=123
RewriteRule ^article/([0-9]+)$ /article.php?id=$1 [L]
# 分类 URL
# /category/tech -> /category.php?cat=tech
RewriteRule ^category/([a-z-]+)$ /category.php?cat=$1 [L]
# 分页 URL
# /page/2 -> /index.php?page=2
RewriteRule ^page/([0-9]+)$ /index.php?page=$1 [L]
# 用户资料
# /user/john -> /user.php?name=john
RewriteRule ^user/([a-zA-Z0-9_-]+)$ /user.php?name=$1 [L]
移除 URL 尾部斜杠 #
apache
# ============================================
# 移除尾部斜杠
# ============================================
RewriteEngine On
# 移除尾部斜杠(目录除外)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# 添加尾部斜杠(仅目录)
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.*)$ /$1/ [L,R=301]
防盗链 #
apache
# ============================================
# 防盗链
# ============================================
RewriteEngine On
# 简单防盗链
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/ [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp)$ - [F,NC,L]
# 返回替代图片
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/ [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ /images/no-hotlink.png [NC,L]
# 允许多个域名
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/ [NC]
RewriteCond %{HTTP_REFERER} !^http://(www\.)?trusted\.com/ [NC]
RewriteCond %{HTTP_REFERER} !^https://(www\.)?example\.com/ [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ - [F,NC,L]
阻止恶意请求 #
apache
# ============================================
# 阻止恶意请求
# ============================================
RewriteEngine On
# 阻止特定 User-Agent
RewriteCond %{HTTP_USER_AGENT} (bot|crawl|spider|scraper) [NC]
RewriteRule ^ - [F,L]
# 阻止特定 IP
RewriteCond %{REMOTE_ADDR} ^192\.168\.1\.100$
RewriteRule ^ - [F,L]
# 阻止访问敏感文件
RewriteRule \.(env|git|svn|htaccess|htpasswd)$ - [F,L]
# 阻止 SQL 注入尝试
RewriteCond %{QUERY_STRING} (union|select|insert|delete|update|drop|script) [NC]
RewriteRule ^ - [F,L]
# 阻止 XSS 尝试
RewriteCond %{QUERY_STRING} (<|>|'|%3C|%3E|%27) [NC]
RewriteRule ^ - [F,L]
网站维护模式 #
apache
# ============================================
# 维护模式
# ============================================
RewriteEngine On
# 启用维护模式
RewriteCond %{REQUEST_URI} !/maintenance\.html$
RewriteCond %{REMOTE_ADDR} !^192\.168\.1\.100$
RewriteRule ^ /maintenance.html [L,R=302]
# 允许特定 IP 访问
RewriteCond %{REQUEST_URI} !/maintenance\.html$
RewriteCond %{REMOTE_ADDR} !^192\.168\.1\.
RewriteRule ^ /maintenance.html [L,R=302]
# 允许静态资源
RewriteCond %{REQUEST_URI} !/maintenance\.html$
RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|gif)$
RewriteCond %{REMOTE_ADDR} !^192\.168\.1\.
RewriteRule ^ /maintenance.html [L,R=302]
调试技巧 #
启用重写日志 #
apache
# ============================================
# 启用重写日志
# ============================================
# Apache 2.4
LogLevel alert rewrite:trace6
# 日志输出到错误日志
# 查看: tail -f /var/log/apache2/error.log | grep rewrite
常见问题 #
text
┌─────────────────────────────────────────────────────────────┐
│ 常见问题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 规则不生效 │
│ - 检查 RewriteEngine On │
│ - 检查 AllowOverride 设置 │
│ - 检查规则顺序 │
│ │
│ 2. 无限重定向循环 │
│ - 添加 [L] 标志 │
│ - 检查条件是否正确 │
│ │
│ 3. 查询字符串丢失 │
│ - 添加 [QSA] 标志 │
│ │
│ 4. CSS/JS 文件无法加载 │
│ - 使用相对路径或绝对路径 │
│ - 添加文件排除规则 │
│ │
│ 5. 子目录规则冲突 │
│ - 使用 RewriteBase │
│ - 调整规则顺序 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
掌握了 URL 重写后,继续学习 SSL/HTTPS 配置,了解如何为网站配置 HTTPS!
最后更新:2026-03-29