.htaccess 配置
.htaccess(Hypertext Access)是 Apache Web 服务器的一个分布式配置文件,允许在目录级别对服务器行为进行配置,而无需修改主配置文件(httpd.conf)。对于 PHP 项目而言,.htaccess 常用于 URL 重写、自定义错误页面、上传限制设置、PHP 配置覆盖等。本节将详细介绍 .htaccess 中的 PHP 配置方法和常见使用场景。
.htaccess 基础
什么是 .htaccess
.htaccess 是 Apache 提供的 分布式配置文件,它允许你在项目的每个目录中放置一个配置文件,该文件中的指令仅对当前目录及其子目录生效。这意味着你可以在不修改服务器主配置的情况下,为不同项目配置不同的 PHP 行为。
启用 .htaccess 支持
要使 .htaccess 生效,需要在 Apache 主配置中启用 AllowOverride:
# 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_ALL 和 PHP_INI_PERDIR 级别的配置。
# 设置字符串值
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):
# 错误处理
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 onphp_admin_value 指令
php_admin_value 与 php_value 功能相同,但它设置的值不能被 ini_set() 或更深层目录的 .htaccess 覆盖。适用于需要强制执行的配置。
# 管理员级别设置(不可被 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_basedir、disable_functions),应该使用 php_admin_value 而非 php_value,以防止用户通过 ini_set() 绕过限制。
注意:php_admin_value 和 php_admin_flag 只能在 Apache 的主配置文件(httpd.conf)或虚拟主机配置中使用,不能在 .htaccess 中使用。
php_admin_flag 指令
# 管理员级别的布尔设置
php_admin_flag display_errors off
php_admin_flag log_errors on
php_admin_flag engine on常见配置示例
自定义错误页面
# 自定义 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
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>上传限制配置
# 上传文件大小限制
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 onURL 重写(前端控制器模式)
几乎所有现代 PHP 框架(Laravel、Symfony、WordPress 等)都使用前端控制器模式,将所有请求路由到 index.php:
# 启用 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
# 强制所有 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 域名
# 统一使用带 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]禁止目录列表
# 禁止目录浏览(防止源码或配置文件被浏览)
Options -Indexes
# 或在不需要的目录放置空 index.html防止访问敏感文件
# 禁止访问 .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>设置默认文件
# 设置目录默认索引文件
DirectoryIndex index.php index.html index.htm
# 如果文件不存在,则显示指定文件
DirectoryIndex index.php index.htmlGzip 压缩
# 启用 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>浏览器缓存控制
# 启用浏览器缓存
<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 跨域配置
# 设置 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 文件:
# 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 重写迁移
# 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 配置迁移
# 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 配置中设置(推荐)禁止访问敏感文件迁移
# 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 压缩迁移
# 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;
}浏览器缓存迁移
# 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 跨域迁移
# 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 是否生效
# 如果 .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 日志调试
# 在虚拟主机配置中(不能放在 .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. 安全问题
# 确保禁止访问 .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 简洁
# .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 包管理器的安装与配置。