第41章:Apache
12 分钟阅读
第四十一章:Apache
Apache HTTP Server(俗称 Apache)是资历最老的 Web 服务器之一——1995 年诞生,而 Nginx 要到 2004 年才出现。近些年 Nginx 在份额上增长很快,但 Apache 凭借丰富的模块生态和 .htaccess 的灵活性(不重载服务器就能改配置,WordPress 这类 CMS 尤其依赖它),依旧稳稳占着一大块地盘。
本章配套视频:Apache配置全攻略,搞定它就能搞定大部分Web服务。
41.1 Apache 安装
41.1.1 apt install apache2
Ubuntu/Debian安装Apache:
| |
| |
| |
| |
安装完成后,访问服务器IP,应该能看到Apache的默认欢迎页面。
41.1.2 a2enmod:启用模块
Apache 的功能几乎都靠模块拼装。Debian / Ubuntu 上用 a2enmod(apache2 enable module)来开关模块:
| |
| |
| |
改完模块必须让 Apache 重新读取配置才生效:
| |
a2enmod做的事其实很简单:在/etc/apache2/mods-enabled/里建两个软链接,指向mods-available/里对应的.load(怎么加载模块)和.conf(模块配置)。所以"启用了模块却不生效",多半是改完忘了reload,或者主配置里缺了IncludeOptional mods-enabled/*.load。另外注意:
a2enmod php8.1只有在装过libapache2-mod-php8.1之后才有意义——a2enmod只负责启用,不会帮你把软件包装上。
41.2 Apache 目录结构
41.2.1 /etc/apache2/
Apache的配置目录结构:
| |
| |
41.2.2 apache2.conf:主配置
| |
| |
41.3 虚拟主机配置
41.3.1 sites-available/:编写站点配置
| |
| |
关键配置项:
ServerName:主域名;ServerAlias:别名(让多个域名指向同一站点)DocumentRoot:网站根目录<Directory ...>:对该目录的访问控制Options -Indexes:禁止目录浏览。少了它,目录里没有index.html时就会把文件清单列出来AllowOverride All:允许该目录下用.htaccess覆盖配置(WordPress 这类 CMS 需要,严格说只需要FileInfo)Require all granted:Apache 2.4 的授权语法,表示允许所有人访问
老教程里的
Order allow,deny/Allow from all在 2.4 上已经失效,那是 2.2 时代的写法。看到这两种语法混在一起,说明配置是从不同年代抄来的。另外
Options +FollowSymLinks和+SymLinksIfOwnerMatch有区别:前者允许跟随指向任何位置的软链接,后者要求软链接的属主与 Apache 运行用户一致。共享主机场景下后者更安全,代价是每个请求都要多做一次检查。
41.3.2 sites-enabled/:启用与禁用站点
a2ensite / a2dissite 负责在 sites-enabled/ 里建立或删除软链接:
| |
| |
| |
改站点配置只需要
reload。restart会短暂中断服务,只有改监听端口、换模块、换 MPM 时才需要。还要注意上面那个
000-default.conf:它是装 Apache 时自带的默认站点,监听*:80且没有ServerName限制。当请求的Host匹配不上任何ServerName时,Apache 会交给第一个加载的 VirtualHost,通常就是它。生产环境一般直接sudo a2dissite 000-default关掉。
41.4 .htaccess:目录级配置
.htaccess 是 Apache 特有的"分布式配置文件":放进网站目录就能生效,不用改主配置、也不用重启服务。WordPress、Drupal 这类 CMS 大量依赖它做 URL 重写等事,这也是不少人选 Apache 的理由。
| |
想让它生效,前提是所在目录的 AllowOverride 不是 None:
| |
AllowOverride All是"方便"换来的取舍,代价值得知道:
- 性能:Apache 处理每个请求时,都要从站点根目录一路往下逐个检查每一层的
.htaccess。目录越深、文件越多,这个开销越明显- 安全:能写
.htaccess就等于能在你的服务器上改配置。如果站点允许用户上传文件、又没限制上传目录的执行权限,攻击者可以塞一个.htaccess把某个脚本按 PHP 执行所以两条建议:能用主配置解决就别用
.htaccess;确实需要时,把AllowOverride收窄到真正需要的类别(最常见的是AllowOverride FileInfo,够 WordPress 重写用),而不是一律All。
41.5 重写规则
41.5.1 mod_rewrite
mod_rewrite是Apache最强大的URL重写模块。
| |
41.5.2 RewriteRule 与常用 flag
基本语法:
| |
| |
常用 flag:
| Flag | 说明 |
|---|---|
R=301 / R=302 | 发送重定向(永久 / 临时)。不写 R 就是内部重写 |
L | Last,本轮的规则匹配到此停止(但在 .htaccess 里,重写后的结果还会被重新处理一轮) |
END | 彻底停止,不再重新处理。想"重写完就结束",END 比 L 更可靠 |
NC | No Case,匹配时不区分大小写 |
QSA | Query String Append,把原 URL 的查询串接到新地址后面 |
NE | No Escape,不对替换结果里的特殊字符做转义(需要保留 #、? 时用) |
F | Forbidden,直接返回 403 |
G | Gone,直接返回 410(资源已永久移除) |
P | Proxy,把请求交给 mod_proxy 转发(需要启用 proxy 模块) |
RewriteCond 是 RewriteRule 的条件,可以叠加多条(默认是"并且",加 [OR] 变成"或者"):
| |
WordPress 的 .htaccess(最常见的 RewriteRule 用法):
| |
这里顺便指出一个真实的坑。网上很常见的写法只有两条条件:
1 2 3RewriteCond %{HTTPS} off [OR] RewriteCond %{HTTP_HOST} !^www\. [NC] RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]它的问题是:当请求"已经是 HTTPS,但域名本来就没带 www"时没毛病;可如果遇到"HTTP + 域名已经带 www",替换目标里再拼一次
www.就会得到https://www.www.example.com/...。上面先用%{HTTP_HOST} ^(?:www\.)?(.+)$把可选的www.吃掉、再用%1引用,就不会重复了。
41.6 认证配置
41.6.1 Basic Auth
HTTP Basic 认证是最简单的 Web 认证方式:浏览器弹出用户名密码框,把 用户名:密码 用 Base64 编码后放进 Authorization 请求头发给服务器。
| |
密码文件内容大致是这样(哈希值仅为示例):
| |
$apr1$是 MD5 变体,$2y$是 bcrypt(用-B参数生成)。另外这个文件必须放在网站根目录之外(/etc/apache2/正合适),否则可能被人直接下载走。
在 .htaccess 或 VirtualHost 里启用:
| |
| |
必须配合 HTTPS 使用。 Basic 认证只是把密码做了 Base64 编码,这不是加密——Base64 解码是零门槛的,网络中间人抓包就直接看到明文密码。所以 Basic Auth 一定要跑在 TLS 之上(内网也建议如此)。
改完配置记得
sudo systemctl reload apache2。
41.6.2 Digest Auth:了解即可
Digest 认证传输的是密码摘要而不是密码本身,理论上比 Basic 安全:
| |
但实际项目中很少用它:Digest 基于 MD5,早已被认为不够强;浏览器端的体验、后端集成也都比较麻烦(很多人第一次配 Digest 会被"realm 名字不一致就登录失败"卡住)。现实中的选择通常是 Basic Auth + HTTPS,或者干脆上 OAuth / OIDC 这类统一认证。
41.7 模块管理
41.7.1 a2enmod
| |
a2enmod只是建立软链接,不会安装软件包。比如a2enmod php8.3需要先装好libapache2-mod-php8.3;启用proxy_fcgi之后,一般还要配SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"才能真正把请求交给 php-fpm。
41.7.2 a2dismod
| |
关掉模块后同样要
sudo apache2ctl configtest && sudo systemctl reload apache2。没把握就别乱关——有些模块(如mpm_*、authz_core)是必需的,关掉会直接起不来。
41.8 MPM:Apache 的并发模型
MPM(Multi-Processing Module)决定 Apache 怎么处理并发连接,直接影响性能与内存占用。Apache 2.4 有三种。
41.8.1 prefork:一个请求一个进程
最传统的模式:每个请求交给一个独立的子进程。进程之间完全隔离,某个请求出问题不影响别人;代价是内存开销大(每个进程几 MB 起步),高并发下内存很快见底。
| |
| |
StartServers:启动时预先创建的进程数MinSpareServers/MaxSpareServers:空闲进程数的上下限,Apache 会在这个范围内动态增减MaxRequestWorkers:同时处理的请求数上限。prefork 下就等于进程数上限,设太大内存会爆MaxConnectionsPerChild:一个子进程处理多少个连接后自我销毁(防止内存泄漏累积)。旧名字是MaxRequestsPerChild,老教程里常见旧名
MaxRequestWorkers该写多少? 先看单个进程的实际内存占用(ps看 RSS),再按"可用内存 ÷ 单进程占用"估算。比如每个进程 30MB、愿意给 Apache 留 1GB,那大约是 30 出头,而不是无脑写 150。
41.8.2 worker:多进程 + 多线程
worker 让每个子进程维护多个线程,一个线程处理一个请求。线程比进程轻得多,同样的内存能撑起更多并发。
| |
注意 MaxRequestWorkers 的含义变了:它是总线程数上限,而同一进程内的线程共享内存,所以总体比 prefork 省得多。前提是所用模块必须线程安全。
41.8.3 event:现代默认,专治长连接
event 建立在 worker 之上,额外解决了一个老问题:Keep-Alive 的空闲连接不再占着工作线程。在 worker / prefork 下,客户端连上来但暂时不发请求,它占用的线程或进程就干等着;连接一多,工作单元被"闲人"占满,新用户就被拒。event 用一个专门的监听线程接管这类连接,工作线程只处理真正有数据的请求。
| |
怎么选? 现代系统(Debian 12 / Ubuntu 22.04 及以后)默认就是 event,一般不用动。
唯一常见的例外是 mod_php:它把 PHP 解释器嵌进 Apache,不是线程安全的,因此只能配 prefork。这也是"用了 mod_php 就享受不到 event 优势"的原因。更好的做法是让 Apache 通过
proxy_fcgi把请求转给独立的php-fpm进程,这样就能安心用 event。
| |
本章小结
本章把 Apache 的日常操作走了一遍:
- 安装:
apt install apache2;用systemctl管理服务,apache2ctl configtest检查语法 - 模块管理:
a2enmod/a2dismod开关模块(本质是mods-enabled/里的软链接),改完要reload - 目录结构:
/etc/apache2/下apache2.conf是主入口,ports.conf管端口,*-available与*-enabled成对出现 - 虚拟主机:在
sites-available/写配置,用a2ensite启用;同端口下第一个加载的 VirtualHost 是默认站点,记得处理000-default .htaccess:目录级配置,灵活但每请求都要逐层查找解析,也带来安全风险;能用主配置就别用,AllowOverride尽量收窄- 重写:
RewriteRule+RewriteCond,[R=301,L]跳转、[L]停止、[END]彻底结束;也要留意上面 41.5.2 指出的重复拼www.的坑 - 认证:
htpasswd做 Basic(务必配 HTTPS),htdigest做 Digest(了解即可,实际少用) - MPM:prefork(进程模型、内存开销大)、worker(进程 + 线程)、event(现代默认,长连接友好);mod_php 只能配 prefork,更推荐改用 php-fpm
Apache 和 Nginx 不是非此即彼:Apache 的 .htaccess 与模块生态让老应用、CMS 部署起来很省心;Nginx 在高并发、静态资源和反向代理上更省资源。真实架构里两者经常同时出现——前面用 Nginx 做入口和负载均衡,后面用 Apache 承载那些依赖 .htaccess 的应用。