Skip to content

.htaccess 配置

.htaccess(Hypertext Access)是 Apache Web 服务器的一个分布式配置文件,允许在目录级别对服务器行为进行配置,而无需修改主配置文件(httpd.conf)。对于 PHP 项目而言,.htaccess 常用于 URL 重写、自定义错误页面、上传限制设置、PHP 配置覆盖等。本节将详细介绍 .htaccess 中的 PHP 配置方法和常见使用场景。

前置知识

在阅读本节之前,你需要了解:

  • Apache Web 服务器的基本配置
  • PHP 配置基础(参见 php.ini 配置
  • HTTP 协议和状态码基础
  • 正则表达式基本语法

.htaccess 基础

什么是 .htaccess

.htaccess 是 Apache 提供的 分布式配置文件,它允许你在项目的每个目录中放置一个配置文件,该文件中的指令仅对当前目录及其子目录生效。这意味着你可以在不修改服务器主配置的情况下,为不同项目配置不同的 PHP 行为。

启用 .htaccess 支持

要使 .htaccess 生效,需要在 Apache 主配置中启用 AllowOverride

apache
# Apache 主配置 httpd.conf 或虚拟主机配置

<Directory /var/www/html>
    # AllowOverride 控制哪些指令可以在 .htaccess 中使用
    # None — 禁用 .htaccess(性能最佳)
    # All — 允许所有指令(最灵活)
    # Options — 允许 Options 指令
    # FileInfo — 允许文件类型和模块指令(含 PHP 配置)
    # AuthConfig — 允许认证相关指令
    AllowOverride All

    # 允许访问
    Require all granted
</Directory>

性能影响

Apache 在处理每个请求时,都会沿着目录路径向上查找 .htaccess 文件。如果目录层级很深且有多个 .htaccess 文件,会导致性能下降。生产环境建议将 .htaccess 中的规则迁移到 <VirtualHost><Directory> 配置中。

PHP 配置指令

Apache 提供了以下指令用于在 .htaccess 中设置 PHP 配置:

php_value 指令

php_value 用于设置 PHP 配置项的值。它只能设置 PHP_INI_ALLPHP_INI_PERDIR 级别的配置。

apache
# 设置字符串值
php_value auto_prepend_file "/var/www/html/bootstrap.php"
php_value error_log "/var/www/html/logs/error.log"
php_value date.timezone "Asia/Shanghai"
php_value include_path ".:/usr/share/php:/var/www/html/lib"
php_value default_charset "UTF-8"

# 设置带单位的数值
php_value memory_limit "256M"
php_value upload_max_filesize "20M"
php_value post_max_size "25M"
php_value max_execution_time "60"

# 设置路径
php_value session.save_path "/var/www/html/sessions"
php_value upload_tmp_dir "/var/www/html/tmp"

# 设置 Session 相关
php_value session.name "MYAPPSESSID"
php_value session.cookie_lifetime "0"
php_value session.cookie_httponly "1"

php_flag 指令

php_flag 用于设置布尔类型的 PHP 配置项(On/Off):

apache
# 错误处理
php_flag display_errors on
php_flag display_startup_errors off
php_flag log_errors on
php_flag html_errors off

# 文件上传
php_flag file_uploads on

# PHP 特性开关
php_flag short_open_tag off
php_flag asp_tags off
php_flag expose_php off

# 安全相关
php_flag allow_url_include off
php_flag allow_url_fopen on

# Session
php_flag session.use_only_cookies on
php_flag session.use_trans_sid off
php_flag session.auto_start off

# 输出控制
php_flag output_buffering on

php_admin_value 指令

php_admin_valuephp_value 功能相同,但它设置的值不能ini_set() 或更深层目录的 .htaccess 覆盖。适用于需要强制执行的配置。

apache
# 管理员级别设置(不可被 ini_set 覆盖)
php_admin_value open_basedir "/var/www/html:/tmp"
php_admin_value disable_functions "exec,passthru,shell_exec,system"
php_admin_value sendmail_path "/usr/sbin/sendmail -t -i"
php_admin_value upload_max_filesize "50M"
php_admin_value error_log "/var/log/php/admin-error.log"

安全建议

对于安全相关的配置项(如 open_basedirdisable_functions),应该使用 php_admin_value 而非 php_value,以防止用户通过 ini_set() 绕过限制。

注意:php_admin_valuephp_admin_flag 只能在 Apache 的主配置文件(httpd.conf)或虚拟主机配置中使用,不能.htaccess 中使用。

php_admin_flag 指令

apache
# 管理员级别的布尔设置
php_admin_flag display_errors off
php_admin_flag log_errors on
php_admin_flag engine on

常见配置示例

自定义错误页面

apache
# 自定义 404 错误页面
ErrorDocument 404 /errors/404.php

# 自定义 403 错误页面
ErrorDocument 403 /errors/403.php

# 自定义 500 错误页面
ErrorDocument 500 /errors/500.php

# 自定义 401 未授权页面
ErrorDocument 401 /errors/401.php

对应的错误页面文件 errors/404.php

php
<?php
declare(strict_types=1);

http_response_code(404);
header('Content-Type: text/html; charset=UTF-8');

// 记录 404 请求
error_log("404 Not Found: " . $_SERVER['REQUEST_URI'] . " from " . $_SERVER['REMOTE_ADDR']);

?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>404 - 页面未找到</title>
</head>
<body>
    <h1>404 - 页面未找到</h1>
    <p>您请求的页面不存在或已被移动。</p>
    <a href="/">返回首页</a>
</body>
</html>

上传限制配置

apache
# 上传文件大小限制
php_value upload_max_filesize "50M"
php_value post_max_size "60M"
php_value max_execution_time "120"
php_value max_input_time "120"
php_value memory_limit "256M"

# 临时上传目录
php_value upload_tmp_dir "/var/www/html/tmp"

# 确保文件上传功能开启
php_flag file_uploads on

URL 重写(前端控制器模式)

几乎所有现代 PHP 框架(Laravel、Symfony、WordPress 等)都使用前端控制器模式,将所有请求路由到 index.php

apache
# 启用 Rewrite 引擎
RewriteEngine On

# 如果请求的不是真实存在的文件
RewriteCond %{REQUEST_FILENAME} !-f

# 如果请求的不是真实存在的目录
RewriteCond %{REQUEST_FILENAME} !-d

# 将所有请求重写到 index.php
RewriteRule ^(.*)$ index.php [QSA,L]

# QSA — Query String Append(保留原有查询参数)
# L   — Last rule(停止后续规则匹配)

URL 重写条件说明

RewriteCond 中常用的测试变量:

  • %{REQUEST_FILENAME} — 请求的完整文件路径
  • %{REQUEST_URI} — 请求的 URI
  • %{HTTP_HOST} — 请求的域名
  • %{HTTPS} — 是否 HTTPS 连接(on/off)
  • -f — 测试是否为存在的文件
  • -d — 测试是否为存在的目录
  • -s — 测试是否为非零大小的文件

强制 HTTPS

apache
# 强制所有 HTTP 请求重定向到 HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# 仅对特定目录强制 HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} ^/admin/
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

强制带 www 域名

apache
# 统一使用带 www 的域名
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301]

# 或统一去除 www
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]

禁止目录列表

apache
# 禁止目录浏览(防止源码或配置文件被浏览)
Options -Indexes

# 或在不需要的目录放置空 index.html

防止访问敏感文件

apache
# 禁止访问 .htaccess 文件本身
<Files ".htaccess">
    Require all denied
</Files>

# 禁止访问特定文件类型
<FilesMatch "\.(env|log|ini|sh|sql|bak|config)$">
    Require all denied
</FilesMatch>

# 禁止访问隐藏文件(如 .git, .env)
<DirectoryMatch "^\.">
    Require all denied
</DirectoryMatch>

# 禁止访问特定目录
<Directory "/var/www/html/vendor">
    Require all denied
</Directory>

<Directory "/var/www/html/.git">
    Require all denied
</Directory>

设置默认文件

apache
# 设置目录默认索引文件
DirectoryIndex index.php index.html index.htm

# 如果文件不存在,则显示指定文件
DirectoryIndex index.php index.html

Gzip 压缩

apache
# 启用 Gzip 压缩(减少传输大小)
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/json
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>

浏览器缓存控制

apache
# 启用浏览器缓存
<IfModule mod_expires.c>
    ExpiresActive On

    # HTML 文件不缓存
    ExpiresByType text/html "access plus 0 seconds"

    # CSS/JS 缓存 1 周
    ExpiresByType text/css "access plus 1 week"
    ExpiresByType application/javascript "access plus 1 week"

    # 图片缓存 1 个月
    ExpiresByType image/jpeg "access plus 1 month"
    ExpiresByType image/png "access plus 1 month"
    ExpiresByType image/gif "access plus 1 month"
    ExpiresByType image/webp "access plus 1 month"
    ExpiresByType image/svg+xml "access plus 1 month"

    # 字体缓存 1 年
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
</IfModule>

# 设置 ETag
<IfModule mod_headers.c>
    Header unset ETag
    FileETag None
</IfModule>

CORS 跨域配置

apache
# 设置 CORS 响应头(允许跨域请求)
<IfModule mod_headers.c>
    # 允许所有域名(开发环境)
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
    Header set Access-Control-Max-Age "3600"

    # 仅允许特定域名(生产环境)
    # Header set Access-Control-Allow-Origin "https://example.com"
</IfModule>

# 处理预检请求
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

实战示例:Laravel 项目的 .htaccess

以下是 Laravel 框架 public/ 目录下标准的 .htaccess 文件:

apache
# Laravel .htaccess
# 将所有请求路由到 index.php(前端控制器模式)

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # 将 Authorization 头从 Apache 转发到 CGI 环境变量
    # 修复 Apache 不传递 Authorization 头的问题
    SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

    # 重定向到 HTTPS(可选)
    # RewriteCond %{HTTPS} off
    # RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # 处理授权头(HTTP Basic Auth)
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # 如果请求的是真实文件或目录,直接提供服务
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    # 否则重写到 index.php
    RewriteRule ^ index.php [L]
</IfModule>

Nginx 环境替代方案

Nginx 不支持 .htaccess 文件。所有在 .htaccess 中的配置都需要迁移到 Nginx 的 server 配置块中。

URL 重写迁移

nginx
# Apache .htaccess
# RewriteEngine On
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^(.*)$ index.php [QSA,L]

# ↓↓↓ Nginx 等价配置 ↓↓↓

server {
    listen 80;
    server_name example.com;
    root /var/www/html/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

PHP 配置迁移

nginx
# Apache .htaccess
# php_value memory_limit 256M
# php_value upload_max_filesize 20M
# php_flag display_errors off

# ↓↓↓ Nginx + PHP-FPM 等价配置 ↓↓↓

# 在 location 或 server 块中设置
location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;

    # PHP 配置覆盖
    fastcgi_param PHP_VALUE "memory_limit=256M
                             upload_max_filesize=20M
                             display_errors=0
                             date.timezone=Asia/Shanghai";
}

# 或者直接在 PHP-FPM 的 pool 配置中设置(推荐)

禁止访问敏感文件迁移

nginx
# Apache .htaccess
# <FilesMatch "\.(env|log|ini|sh|sql)$">
#     Require all denied
# </FilesMatch>

# ↓↓↓ Nginx 等价配置 ↓↓↓

location ~ /\.(env|log|ini|sh|sql|bak|config)$ {
    deny all;
    return 404;
}

location ~ /\.git {
    deny all;
    return 404;
}

location ~ /\.ht {
    deny all;
    return 404;
}

Gzip 压缩迁移

nginx
# Apache .htaccess 中的 Gzip 配置
# ↓↓↓ Nginx 等价配置 ↓↓↓

http {
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_vary on;
}

浏览器缓存迁移

nginx
# Apache .htaccess 中的缓存控制
# ↓↓↓ Nginx 等价配置 ↓↓↓

server {
    # 静态资源缓存
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # HTML 不缓存
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }
}

CORS 跨域迁移

nginx
# Apache .htaccess 中的 CORS 配置
# ↓↓↓ Nginx 等价配置 ↓↓↓

server {
    # CORS 配置
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';

    # 处理预检请求
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        add_header 'Access-Control-Max-Age' 3600;
        return 204;
    }
}

.htaccess 调试技巧

查看 .htaccess 是否生效

apache
# 如果 .htaccess 没有生效,检查以下项:

# 1. mod_rewrite 是否启用
# 检查 httpd.conf 中是否有:
LoadModule rewrite_module modules/mod_rewrite.so

# 2. AllowOverride 是否设置正确
<Directory /var/www/html>
    AllowOverride All    # 必须至少包含 FileInfo 或 All
</Directory>

# 3. .htaccess 文件名和权限是否正确
# 文件名必须是 .htaccess(以点开头)
# 文件权限:644(所有者可读写,其他只读)

# 4. 查看 Apache 错误日志
# tail -f /var/log/apache2/error.log

启用 Rewrite 日志调试

apache
# 在虚拟主机配置中(不能放在 .htaccess 中)启用 Rewrite 日志
<VirtualHost *:80>
    ServerName example.com

    # 开启 Rewrite 日志(仅调试时使用)
    RewriteLogLevel 3
    RewriteLog /var/log/apache2/rewrite.log

    # ...
</VirtualHost>

注意事项

1. 修改后无需重启

.htaccess 文件中的配置在每次请求时被读取(Apache 会缓存 .htaccess),所以修改后通常立即生效,无需重启 Apache。但某些复杂配置可能需要清除浏览器缓存。

2. 配置冲突

当多个目录层级都存在 .htaccess 时,子目录的配置会覆盖父目录的配置(同名的 php_value/php_flag)。但不一定完全覆盖,具体取决于指令类型。

3. 安全问题

apache
# 确保禁止访问 .htaccess 本身
<Files ".htaccess">
    Require all denied
</Files>

# 确保 .env 文件不被访问
<Files ".env">
    Require all denied
</Files>

# 不要将数据库密码或敏感信息写在 .htaccess 中

最佳实践

1. 生产环境避免使用 .htaccess

性能对比:
- 将规则写在 httpd.conf 的 <VirtualHost> 中 → 仅读取一次,性能最优
- 使用 .htaccess → 每次请求都要检查文件,性能降低
- 多层 .htaccess → 性能降低更多

推荐做法:
- 开发环境:使用 .htaccess(方便快速修改)
- 生产环境:将规则迁移到 Nginx 配置或 Apache <VirtualHost> 中

2. 保持 .htaccess 简洁

apache
# .htaccess 应该只包含必要的规则
# 将通用的、与项目无关的配置放在服务器主配置中

# 推荐:简洁的 .htaccess
<IfModule mod_rewrite.c>
    Options -MultiViews -Indexes
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.php [L]
</IfModule>

# 避免:在 .htaccess 中堆积大量配置

下一节

你已经了解了 .htaccess 的配置方法和 Nginx 替代方案。接下来将进入 Composer 部分,学习 Composer 包管理器的安装与配置。

参考链接