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