Apache HTTP Server 完全指南 / 从 Nginx 迁移
从 Nginx 迁移
从 Nginx 迁移到 Apache 需要理解两者配置差异,本章提供详细的配置映射和迁移策略。
1. 架构差异
1.1 核心区别
| 特性 | Nginx | Apache |
|---|---|---|
| 架构 | 事件驱动 | 进程/线程模型 |
| 配置 | 块式声明 | 指令式 |
| .htaccess | 不支持 | 支持 |
| 模块 | 编译时静态 | 动态/静态 |
| URL 重写 | rewrite | mod_rewrite |
| 反向代理 | proxy_pass | ProxyPass |
| 负载均衡 | upstream | Proxy balancer |
| 虚拟主机 | server {} | VirtualHost |
1.2 配置文件结构对比
Nginx:
/etc/nginx/
├── nginx.conf # 主配置
├── conf.d/ # 额外配置
├── sites-available/ # 可用站点
├── sites-enabled/ # 启用站点
└── modules-enabled/ # 启用模块
Apache:
/etc/apache2/
├── apache2.conf # 主配置
├── ports.conf # 端口配置
├── conf-available/ # 可用配置
├── conf-enabled/ # 启用配置
├── mods-available/ # 可用模块
├── mods-enabled/ # 启用模块
├── sites-available/ # 可用站点
└── sites-enabled/ # 启用站点
2. 配置映射
2.1 基本服务器配置
Nginx:
worker_processes auto;
worker_connections 1024;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
Apache:
# MPM 配置
<IfModule mpm_event_module>
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 10000
</IfModule>
# 基本配置
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
ServerTokens Prod
ServerSignature Off
2.2 虚拟主机映射
Nginx:
server {
listen 80;
server_name www.example.com example.com;
root /var/www/example.com/public;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
error_log /var/log/nginx/example-error.log;
access_log /var/log/nginx/example-access.log;
}
Apache:
<VirtualHost *:80>
ServerName www.example.com
ServerAlias example.com
DocumentRoot /var/www/example.com/public
<Directory "/var/www/example.com/public">
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
# try_files 等价
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
</IfModule>
</Directory>
# PHP-FPM
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
</FilesMatch>
# 静态文件缓存
<IfModule mod_expires.c>
<LocationMatch "\.(jpg|jpeg|png|gif|ico|css|js|woff2)$">
ExpiresDefault "access plus 1 year"
Header set Cache-Control "public, immutable"
</LocationMatch>
</IfModule>
ErrorLog /var/log/apache2/example-error.log
CustomLog /var/log/apache2/example-access.log combined
</VirtualHost>
2.3 反向代理映射
Nginx:
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Apache:
# 启用模块
# sudo a2enmod proxy proxy_http headers
<Proxy balancer://backend>
BalancerMember http://127.0.0.1:8080
BalancerMember http://127.0.0.1:8081
ProxySet lbmethod=byrequests
</Proxy>
<VirtualHost *:80>
ServerName api.example.com
ProxyPreserveHost On
ProxyPass / balancer://backend/
ProxyPassReverse / balancer://backend/
RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-Proto "http"
</VirtualHost>
2.4 负载均衡映射
Nginx:
upstream app {
least_conn;
server app1.example.com weight=3;
server app2.example.com weight=2;
server app3.example.com backup;
keepalive 32;
}
server {
location / {
proxy_pass http://app;
}
}
Apache:
<Proxy balancer://app>
BalancerMember http://app1.example.com loadfactor=3
BalancerMember http://app2.example.com loadfactor=2
BalancerMember http://app3.example.com status=+H
ProxySet lbmethod=bybusyness
</Proxy>
<VirtualHost *:80>
ServerName www.example.com
ProxyPass / balancer://app/
ProxyPassReverse / balancer://app/
</VirtualHost>
2.5 SSL 配置映射
Nginx:
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
}
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
Apache:
# HTTP 重定向
<VirtualHost *:80>
ServerName www.example.com
RewriteEngine On
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
# HTTPS 站点
<VirtualHost *:443>
ServerName www.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.crt
SSLCertificateKeyFile /etc/ssl/private/example.com.key
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off
Header always set Strict-Transport-Security "max-age=31536000"
Header always set X-Content-Type-Options "nosniff"
Protocols h2 http/1.1
</VirtualHost>
2.6 URL 重写映射
Nginx:
# 重定向
location /old-page {
return 301 /new-page;
}
# 正则重写
rewrite ^/blog/(\d+)/(.*)$ /posts/$1/$2 permanent;
# 条件重写
if ($http_host ~* ^www\.(.*)$) {
return 301 https://$1$request_uri;
}
Apache:
# 重定向
Redirect 301 /old-page /new-page
# mod_rewrite 重写
RewriteEngine On
RewriteRule ^/blog/(\d+)/(.*)$ /posts/$1/$2 [R=301,L]
# 条件重写
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
2.7 缓存配置映射
Nginx:
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
Apache:
<IfModule mod_expires.c>
<LocationMatch "\.(jpg|jpeg|png|gif|ico|css|js)$">
ExpiresDefault "access plus 1 year"
Header set Cache-Control "public"
</LocationMatch>
</IfModule>
# 不记录静态文件日志
SetEnvIf Request_URI "\.(jpg|jpeg|png|gif|ico|css|js)$" dontlog
CustomLog /var/log/apache2/access.log combined env=!dontlog
3. 功能映射表
3.1 核心功能
| Nginx 功能 | Apache 等价 |
|---|---|
server {} | <VirtualHost> |
location {} | <Directory>, <Location> |
listen | Listen |
server_name | ServerName, ServerAlias |
root | DocumentRoot |
index | DirectoryIndex |
try_files | mod_rewrite |
return 301 | Redirect 301 |
rewrite | RewriteRule |
proxy_pass | ProxyPass |
fastcgi_pass | SetHandler proxy:fcgi:// |
error_page | ErrorDocument |
access_log | CustomLog |
error_log | ErrorLog |
expires | mod_expires |
add_header | Header set |
gzip | mod_deflate |
3.2 变量映射
| Nginx 变量 | Apache 变量 |
|---|---|
$host | %{HTTP_HOST}i |
$remote_addr | %{REMOTE_ADDR}s |
$request_uri | %{REQUEST_URI}s |
$query_string | %{QUERY_STRING}s |
$scheme | %{REQUEST_SCHEME}s |
$server_port | %{SERVER_PORT}s |
$http_user_agent | %{HTTP_USER_AGENT}i |
$http_referer | %{HTTP_REFERER}i |
$request_method | %{REQUEST_METHOD}s |
$uri | %{REQUEST_URI}s |
$args | %{QUERY_STRING}s |
$cookie_name | %{COOKIE_name}i |
3.3 标志映射
| Nginx 标志 | Apache 标志 |
|---|---|
permanent (301) | [R=301,L] |
redirect (302) | [R=302,L] |
last | [L] |
break | [L] |
last (rewrite) | [L] |
flag (proxy) | [P,L] |
4. 渐进迁移策略
4.1 迁移计划
阶段 1:并行部署(1-2 周)
├── 安装和配置 Apache
├── 同步网站文件
├── 在非生产环境测试
└── 修复兼容性问题
阶段 2:灰度切换(1 周)
├── 切换 10% 流量到 Apache
├── 监控性能和错误
├── 逐步增加流量比例
└── 持续观察
阶段 3:全面切换(1-2 天)
├── 切换 100% 流量
├── 保留 Nginx 作为备份
├── 监控 24 小时
└── 确认稳定后下线 Nginx
4.2 灰度发布
使用负载均衡器:
# Nginx 作为前端(灰度阶段)
upstream old_backend {
server 127.0.0.1:8080; # Nginx
}
upstream new_backend {
server 127.0.0.1:8081; # Apache
}
split_clients "${remote_addr}" $backend {
10% new_backend;
* old_backend;
}
server {
location / {
proxy_pass http://$backend;
}
}
使用 DNS 权重:
www.example.com A 192.168.1.100 # Nginx (90% 流量)
www.example.com A 192.168.1.101 # Apache (10% 流量)
4.3 验证清单
#!/bin/bash
# migration-verify.sh
APACHE_URL="http://localhost:8081"
NGINX_URL="http://localhost:8080"
# 测试 URL 列表
URLS=(
"/"
"/about"
"/api/data"
"/static/style.css"
"/images/logo.png"
)
echo "=== 迁移验证 ==="
for url in "${URLS[@]}"; do
APACHE_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$APACHE_URL$url")
NGINX_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$NGINX_URL$url")
if [ "$APACHE_CODE" == "$NGINX_CODE" ]; then
echo "✅ $url - Apache: $APACHE_CODE, Nginx: $NGINX_CODE"
else
echo "❌ $url - Apache: $APACHE_CODE, Nginx: $NGINX_CODE"
fi
done
5. 常见迁移问题
5.1 try_files 等价
# Nginx: try_files $uri $uri/ /index.php?$query_string;
# Apache (mod_rewrite)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
</IfModule>
# 或使用 FallbackResource
<Directory "/var/www/html">
FallbackResource /index.php
</Directory>
5.2 嵌套 location
# Nginx:
# location /api/v1/ {
# location /api/v1/users { ... }
# }
# Apache 使用嵌套 Directory 或 Location
<Location "/api/v1">
# API v1 通用配置
<Location "/api/v1/users">
# 用户端点配置
</Location>
</Location>
5.3 条件判断
# Nginx:
# if ($request_method = POST) { ... }
# Apache 使用 mod_rewrite
RewriteCond %{REQUEST_METHOD} ^POST$ [NC]
RewriteRule ^ /handle-post.php [L]
# 或使用 <If>
<If "%{REQUEST_METHOD} == 'POST'">
# POST 请求配置
</If>
5.4 map 等价
# Nginx:
# map $http_host $backend {
# default 127.0.0.1:8080;
# ~^api 127.0.0.1:3000;
# }
# Apache 使用 RewriteMap
RewriteMap backend txt:/etc/apache2/backend.map
# backend.map 内容:
# api.example.com 127.0.0.1:3000
# default 127.0.0.1:8080
RewriteCond ${backend:%{HTTP_HOST}|default} ^(.+)$
RewriteRule ^(.*)$ http://%1/$1 [P,L]
6. 性能对比
6.1 基准测试
# 静态文件测试
# Nginx
wrk -t12 -c400 -d30s http://nginx-server/index.html
# Apache
wrk -t12 -c400 -d30s http://apache-server/index.html
# PHP 测试
# Nginx + PHP-FPM
wrk -t12 -c100 -d30s http://nginx-server/info.php
# Apache + PHP-FPM
wrk -t12 -c100 -d30s http://apache-server/info.php
6.2 预期差异
| 场景 | Nginx | Apache | 说明 |
|---|---|---|---|
| 静态文件 | 更快 | 略慢 | Nginx 事件驱动优势 |
| PHP | 相当 | 相当 | 都使用 PHP-FPM |
| 反向代理 | 更快 | 相当 | Nginx 代理更轻量 |
| .htaccess | 不支持 | 支持 | Apache 独有功能 |
| 内存使用 | 更低 | 更高 | 进程模型差异 |
7. 回滚计划
7.1 快速回滚
#!/bin/bash
# rollback-to-nginx.sh
# 1. 切换端口
sudo sed -i 's/Listen 80/Listen 8081/' /etc/apache2/ports.conf
sudo systemctl restart apache2
# 2. 恢复 Nginx
sudo sed -i 's/listen 8080/listen 80/' /etc/nginx/sites-enabled/default
sudo systemctl restart nginx
# 3. 更新负载均衡/防火墙规则
# 根据实际环境配置
echo "已回滚到 Nginx"
7.2 数据同步
# 同步网站文件
rsync -avz /var/www/nginx/ /var/www/apache/
# 同步 SSL 证书
rsync -avz /etc/nginx/ssl/ /etc/apache2/ssl/
# 同步日志(用于分析)
rsync -avz /var/log/nginx/ /var/log/nginx-backup/
8. 业务场景
8.1 WordPress 迁移
<VirtualHost *:80>
ServerName wordpress.example.com
DocumentRoot /var/www/wordpress
<Directory "/var/www/wordpress">
AllowOverride All
Require all granted
# WordPress 伪静态
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
</Directory>
</VirtualHost>
8.2 Laravel 迁移
<VirtualHost *:80>
ServerName laravel.example.com
DocumentRoot /var/www/laravel/public
<Directory "/var/www/laravel/public">
AllowOverride None
Require all granted
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
</Directory>
</VirtualHost>
9. 注意事项
- 充分测试:迁移前在测试环境全面测试
- 灰度发布:使用灰度发布降低风险
- 保留回滚:保留 Nginx 配置和环境
- 监控告警:迁移期间加强监控
- 性能验证:迁移后进行性能基准测试
10. 扩展阅读
11. 总结
从 Nginx 迁移到 Apache 需要:
- 理解差异:掌握两者架构和配置差异
- 配置映射:将 Nginx 配置正确转换为 Apache
- 渐进迁移:使用灰度发布降低风险
- 充分验证:功能、性能、安全全面验证
- 保留回滚:保留快速回滚能力
Apache 在 .htaccess、动态模块、兼容性等方面有独特优势,合理迁移可以获得更好的运维体验。