1. 为什么源码编译安装依然值得折腾
源码编译安装 Apache 这件事,放在今天这个包管理器满天飞的时代,看起来确实有点“复古”。apt install apache2或者yum install httpd一条命令就能搞定的事,为什么要花半小时甚至更久去编译?我刚开始接触 Linux 运维的时候也有这个疑问,直到在实际工作中踩了几次坑才明白:包管理器给你的是“通用答案”,而源码编译给你的是“定制答案”。
最直接的场景就是你需要特定的模块组合。比如你要在一台内网机器上跑一个只服务于内部系统的 HTTP 服务,不需要 LDAP 认证、不需要 Lua 脚本支持、不需要 mod_security,但你需要把 mod_http2 和 mod_proxy 编进去,同时把 prefork MPM 换成 event MPM。包管理器安装的版本往往把一堆你可能永远用不到的模块都编译进去了,而源码编译可以让你精确控制每一个--enable和--disable开关。另一个常见场景是版本锁定——某些老旧的业务系统对 Apache 版本有严格要求,比如必须用 2.4.52 而不能用 2.4.58,因为某个模块的行为在新版本里发生了变化。这时候源码编译就是唯一的选择。
还有一类需求是路径定制。包管理器安装的 Apache 默认把配置文件放在/etc/httpd或/etc/apache2,二进制文件放在/usr/sbin,日志放在/var/log。但有些公司的运维规范要求所有自建服务统一放在/opt下面,配置文件、日志、二进制全部自包含,方便迁移和备份。源码编译配合--prefix参数可以完美满足这个需求,整个 Apache 就是一个目录,打包带走就能在另一台机器上跑起来。
当然,源码编译也有它的代价。你需要手动处理依赖关系,需要自己写 systemd 服务文件或者 init 脚本,升级的时候要重新编译。所以我的建议是:如果你只是想要一个能跑的 Web 服务器,包管理器足够了;但如果你需要精确控制编译选项、需要特定版本、或者需要把服务完全自包含,那源码编译才是正解。这篇文章就是写给后一种人的。
2. 编译之前的准备工作
2.1 依赖包的安装与确认
编译 Apache 之前,有几个依赖是必须提前装好的。这些东西不装,configure阶段就会报错,而且报错信息有时候不太直观,新手容易卡在这里。
首先是APR(Apache Portable Runtime)和APR-util。这两个是 Apache 的底层运行时库,负责跨平台的文件操作、网络通信、内存管理等等。虽然 Apache 源码包里自带了一份 APR 的副本,但我强烈建议你单独编译安装 APR 和 APR-util,然后用--with-apr和--with-apr-util指定路径。原因很简单:自带的副本版本可能比较老,而且多个 Apache 实例共享同一份 APR 也更省资源。
其次是PCRE(Perl Compatible Regular Expressions)。Apache 的mod_rewrite模块依赖它来做正则匹配。如果你的系统里没有 PCRE 开发包,configure会直接报错说找不到 pcre-config。注意这里要装的是pcre-devel而不是pcre,前者包含头文件和静态库,后者只有运行时库。
然后是OpenSSL开发包。如果你要启用mod_ssl(基本上都要),就需要 OpenSSL 的头文件和库。同样注意装openssl-devel而不是openssl。
最后是编译器工具链:gcc、make、autoconf、libtool。这些在大多数发行版的最小化安装里都不带,需要手动装。
在基于 RPM 的系统上,一条命令搞定:
yum install -y gcc gcc-c++ make autoconf libtool \ pcre-devel openssl-devel expat-devel \ libxml2-devel zlib-devel在基于 Debian 的系统上:
apt-get install -y build-essential autoconf libtool \ libpcre3-dev libssl-dev libexpat1-dev \ libxml2-dev zlib1g-dev注意:
expat-devel和libxml2-devel是为了支持mod_dav和mod_xml2enc等模块。如果你确定不需要这些模块,可以不装,但装了也没坏处,编译时不用就是了。
2.2 源码包的下载与校验
Apache 的源码包可以从官方镜像站下载。我一般习惯把源码统一放在/usr/local/src下面,方便管理。下载的时候注意两个文件:一个是源码压缩包httpd-2.4.xx.tar.gz,另一个是对应的签名文件.asc。校验签名这一步很多人会跳过,但在生产环境里我建议还是做一下,确保下载的文件没有被篡改或者下载不完整。
cd /usr/local/src wget https://archive.apache.org/dist/httpd/httpd-2.4.58.tar.gz wget https://archive.apache.org/dist/httpd/httpd-2.4.58.tar.gz.asc gpg --verify httpd-2.4.58.tar.gz.asc httpd-2.4.58.tar.gz如果提示找不到公钥,需要先导入 Apache 的发布签名密钥。这一步的具体命令我就不展开了,官方文档里有详细说明。校验通过之后,解压源码包:
tar -xzf httpd-2.4.58.tar.gz cd httpd-2.4.582.3 目录规划与用户创建
在编译之前,先想清楚你的目录结构。我通常采用这样的布局:
| 目录 | 用途 |
|---|---|
/opt/apache | Apache 安装根目录 |
/opt/apache/conf | 配置文件 |
/opt/apache/logs | 日志文件 |
/opt/apache/htdocs | 默认网站根目录 |
/opt/apache/modules | 动态加载的模块 |
/opt/apache/bin | 可执行文件 |
这种布局的好处是所有东西都在一个目录下,备份的时候直接打包/opt/apache就行,迁移到另一台机器上只要路径一致就能直接跑。
另外,Apache 默认以daemon用户运行,但这个用户在很多系统上已经存在且可能有其他用途。我建议创建一个专用的用户和组:
groupadd -r apache useradd -r -g apache -s /sbin/nologin -d /opt/apache apache-r表示创建系统用户,-s /sbin/nologin表示这个用户不能登录 shell,-d /opt/apache指定家目录。这样 Apache 进程就以apache用户的身份运行,权限最小化,安全性更好。
3. 编译配置的核心参数拆解
3.1 configure 参数逐项解析
configure是整个编译过程中最关键的一步,它决定了你的 Apache 最终长什么样。参数写错了,后面编译出来的东西可能完全不是你想要的。下面我逐项拆解常用的参数。
--prefix=/opt/apache指定安装根目录。这个不用多说,所有文件都会安装到这个目录下面。
--with-apr=/usr/local/apr和--with-apr-util=/usr/local/apr-util指定 APR 和 APR-util 的安装路径。前提是你已经单独编译安装了这两个库。如果你不想单独装,也可以不指定这两个参数,Apache 会用自带的副本,但我不推荐这么做。
--with-pcre=/usr/bin/pcre-config指定 PCRE 的配置脚本路径。大多数情况下configure能自动找到,但如果你的 PCRE 装在非标准路径下,就需要手动指定。
--with-ssl=/usr/include/openssl指定 OpenSSL 的头文件路径。同样,标准路径下不需要指定,非标准路径才需要。
--enable-so是最重要的参数之一。它允许 Apache 在运行时动态加载模块(DSO,Dynamic Shared Object)。没有这个参数,所有模块都必须静态编译进主程序,以后想加个模块就得重新编译整个 Apache。有了它,你可以用LoadModule指令在配置文件里灵活加载.so文件。
--enable-rewrite启用mod_rewrite模块,用于 URL 重写。这个模块几乎是必备的,WordPress、Discuz 之类的应用都依赖它来做伪静态。
--enable-ssl启用mod_ssl模块,支持 HTTPS。这个也不用多说,现在没有 HTTPS 的网站基本没法看。
--enable-headers启用mod_headers,允许你修改 HTTP 响应头。做跨域配置、缓存控制的时候经常用到。
--enable-expires启用mod_expires,控制静态资源的过期时间,对性能优化很有帮助。
--enable-deflate启用mod_deflate,对响应内容进行 gzip 压缩,能显著减少传输数据量。
--enable-proxy和--enable-proxy-http启用代理模块,做反向代理的时候需要。
--enable-http2启用 HTTP/2 支持。注意这个模块依赖nghttp2库,需要提前安装libnghttp2-devel。
--with-mpm=event指定使用 event MPM。Apache 2.4 支持三种 MPM:prefork、worker、event。prefork 是每个请求一个进程,稳定但资源消耗大;worker 是每个进程多个线程,资源利用率高但一个线程崩溃可能影响同进程的其他线程;event 在 worker 的基础上进一步优化,用异步方式处理 keep-alive 连接,是高并发场景下的首选。
--disable-status之类的参数用来禁用不需要的模块。比如你不需要查看服务器状态页面,就可以禁掉mod_status,减少攻击面。
一个完整的configure命令大概长这样:
./configure \ --prefix=/opt/apache \ --with-apr=/usr/local/apr \ --with-apr-util=/usr/local/apr-util \ --with-pcre=/usr/bin/pcre-config \ --with-ssl=/usr/include/openssl \ --enable-so \ --enable-rewrite \ --enable-ssl \ --enable-headers \ --enable-expires \ --enable-deflate \ --enable-proxy \ --enable-proxy-http \ --enable-http2 \ --with-mpm=event \ --disable-status \ --disable-autoindex \ --disable-userdir3.2 模块选择背后的取舍逻辑
上面那些--enable和--disable不是随便写的,每一个背后都有考量。我拿几个典型的来说说。
为什么禁用 autoindex?mod_autoindex的作用是当目录下没有index.html时,自动生成一个文件列表页面。这个功能在开发环境里挺方便,但在生产环境里是个安全隐患——攻击者可以通过它浏览你的目录结构,找到本不该暴露的文件。所以生产环境我一般直接禁掉。
为什么禁用 userdir?mod_userdir允许通过/~username的方式访问用户家目录下的网站。这个功能在共享主机环境里有用,但在自己的服务器上完全没必要,而且容易造成权限混乱。
为什么启用 deflate 而不是用 PHP 的 gzip?如果压缩工作在 Apache 层面完成,PHP 就不需要再处理一遍,减少了 PHP 进程的负担。而且mod_deflate可以对静态文件(HTML、CSS、JS)也进行压缩,这是 PHP 做不到的。
为什么用 event MPM 而不是 prefork?前面说了,event 在高并发下表现更好。但有一个例外:如果你需要用mod_php(把 PHP 作为 Apache 模块加载),那就只能用 prefork,因为mod_php不是线程安全的。不过现在主流做法都是用 PHP-FPM 配合mod_proxy_fcgi,所以 event MPM 是更好的选择。
实操心得:第一次编译的时候,建议把所有
--enable和--disable都记在一个文本文件里,后面升级版本的时候直接复制粘贴,不用重新想一遍。我见过太多人升级 Apache 的时候忘了当初编译了哪些模块,结果新版本跑起来发现某个功能没了。
3.3 编译与安装的实操记录
configure跑完之后,如果最后输出没有报错,就可以开始编译了。编译用make,安装用make install。这两个命令看起来简单,但有几个细节值得注意。
首先是make的并行编译。默认情况下make是单线程的,编译 Apache 大概需要几分钟。如果你的机器有多核 CPU,可以用-j参数加速:
make -j$(nproc)$(nproc)会自动获取 CPU 核心数。比如 8 核的机器就用make -j8,编译时间能缩短到原来的几分之一。但注意不要设置得太大,一般不超过核心数的两倍,否则可能因为内存不足导致编译失败。
编译过程中如果报错,最常见的两类问题:一是找不到某个头文件,说明对应的-devel包没装;二是某个函数未定义,通常是库的版本不匹配。遇到报错不要慌,仔细看错误信息里提到的文件名和函数名,基本就能定位到问题。
编译完成后安装:
make install安装过程很快,就是把编译好的文件复制到--prefix指定的目录下。安装完成后,检查一下目录结构:
ls -la /opt/apache/你应该能看到bin、conf、htdocs、logs、modules等目录。如果这些都有了,说明安装成功。
4. 安装后的配置与调优
4.1 主配置文件的精简与调整
安装完成后,/opt/apache/conf/httpd.conf是主配置文件。默认的配置文件有一千多行,里面大部分是注释和示例,实际生效的配置并不多。我一般会先备份一份原始配置,然后按照自己的需求精简。
首先修改ServerRoot和DocumentRoot:
ServerRoot "/opt/apache" DocumentRoot "/opt/apache/htdocs"然后确认Listen指令。默认是监听 80 端口:
Listen 80如果你需要 HTTPS,还要加上:
Listen 443接下来是用户和组的配置。找到User和Group指令,改成前面创建的apache用户:
User apache Group apache然后是模块加载部分。默认配置里有一大堆LoadModule指令,你需要根据实际编译的模块来调整。比如你编译时启用了--enable-rewrite,那就要确保这一行没有被注释掉:
LoadModule rewrite_module modules/mod_rewrite.so一个常见的坑是:编译时启用了某个模块,但配置文件里忘了加载,结果功能不生效。排查的时候可以用httpd -M命令列出所有已加载的模块,对比一下你期望的模块列表。
4.2 日志目录与权限设置
Apache 需要向日志目录写入文件,所以logs目录的权限必须正确。默认情况下,make install会把logs目录的所有者设为root,但 Apache 进程是以apache用户运行的,没有写入权限。需要手动修改:
chown -R apache:apache /opt/apache/logs chmod 755 /opt/apache/logs同样,htdocs目录也需要确保apache用户有读取权限。如果是静态网站,755就够了;如果需要上传文件,可能要给775或者把所有者改成apache。
注意:不要图省事把整个
/opt/apache目录都改成apache用户所有。配置文件和二进制文件应该保持root所有,只有日志和需要写入的目录才给apache用户权限。这是最小权限原则的基本要求。
4.3 服务管理脚本的编写
源码编译安装的 Apache 没有自动生成 systemd 服务文件,需要手动创建。在/etc/systemd/system/下新建一个apache.service:
[Unit] Description=Apache HTTP Server After=network.target [Service] Type=forking ExecStart=/opt/apache/bin/apachectl start ExecStop=/opt/apache/bin/apachectl stop ExecReload=/opt/apache/bin/apachectl graceful PIDFile=/opt/apache/logs/httpd.pid PrivateTmp=true [Install] WantedBy=multi-user.target然后重新加载 systemd 配置并启动服务:
systemctl daemon-reload systemctl enable apache systemctl start apache检查服务状态:
systemctl status apache如果看到active (running),说明服务启动成功。再用curl测试一下:
curl -I http://127.0.0.1应该返回HTTP/1.1 200 OK。
5. 常见问题与排查技巧实录
5.1 编译阶段的高频报错
编译 Apache 时遇到的报错,八成集中在依赖缺失上。我整理了一个速查表:
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
configure: error: APR not found | 没装 APR 或路径不对 | 安装 APR 并用--with-apr指定路径 |
configure: error: APR-util not found | 同上 | 安装 APR-util 并指定路径 |
configure: error: pcre-config for libpcre not found | 没装 PCRE 开发包 | 安装pcre-devel或libpcre3-dev |
configure: error: OpenSSL version is too old | OpenSSL 版本太低 | 升级 OpenSSL 或指定新版本路径 |
make: *** No targets specified and no makefile found | configure没成功执行 | 检查configure的输出,解决报错后重新执行 |
undefined reference to 'SSL_CTX_new' | 链接时找不到 OpenSSL 库 | 确认--with-ssl路径正确,或加LDFLAGS |
还有一个比较隐蔽的问题:configure成功执行了,但make到一半报错说某个.h文件找不到。这种情况通常是头文件路径没有正确传递给编译器。可以尝试在configure时加上CPPFLAGS和LDFLAGS:
CPPFLAGS="-I/usr/local/apr/include/apr-1" \ LDFLAGS="-L/usr/local/apr/lib" \ ./configure ...5.2 启动阶段的典型故障
Apache 编译安装成功,但启动时报错,这类问题也很常见。最典型的是端口被占用:
(98)Address already in use: AH00072: make_sock: could not bind to address [::]:80这说明 80 端口已经被其他程序占用了。用ss -tlnp | grep :80或者netstat -tlnp | grep :80查看是哪个进程占用的。如果是系统自带的 HTTP 服务,先停掉它:
systemctl stop httpd systemctl disable httpd另一个常见问题是配置文件语法错误:
AH00526: Syntax error on line 52 of /opt/apache/conf/httpd.conf这种报错会明确告诉你行号,直接去看那一行就行了。常见的语法错误包括:指令拼写错误、参数类型不对、模块没加载但使用了该模块的指令。用httpd -t可以只检查配置文件语法而不启动服务:
/opt/apache/bin/httpd -t如果输出Syntax OK,说明配置文件没问题。
还有一个坑是 SELinux。在某些发行版上,SELinux 默认是 enforcing 模式,会阻止 Apache 绑定非标准端口或者访问非标准目录。如果确认是 SELinux 的问题,可以临时设为 permissive 模式来验证:
setenforce 0如果设为 permissive 后问题消失,那就需要为 Apache 配置合适的 SELinux 策略,而不是直接关掉 SELinux。具体策略配置这里不展开,但记住一点:生产环境不要长期关闭 SELinux。
5.3 运行阶段的性能与安全调优
Apache 跑起来之后,还需要根据实际负载做一些调优。event MPM 的核心参数在httpd-mpm.conf里(或者直接写在httpd.conf里):
<IfModule mpm_event_module> StartServers 3 MinSpareThreads 75 MaxSpareThreads 250 ThreadsPerChild 25 MaxRequestWorkers 400 MaxConnectionsPerChild 10000 </IfModule>这几个参数的含义:StartServers是启动时的进程数;MinSpareThreads和MaxSpareThreads控制空闲线程的数量范围;ThreadsPerChild是每个进程的线程数;MaxRequestWorkers是最大并发请求数,等于进程数乘以每进程线程数;MaxConnectionsPerChild是每个进程处理多少个请求后重启,设为 0 表示不重启,但建议设一个非零值来避免内存泄漏累积。
调优的时候不要凭感觉,要看实际数据。mod_status可以提供一个实时状态页面,显示当前有多少请求在处理、多少线程空闲、总共处理了多少请求。虽然前面说生产环境可以禁用mod_status,但在调优阶段临时启用一下是很有帮助的。
安全方面,有几个配置是必须做的。首先是隐藏 Apache 版本信息:
ServerTokens Prod ServerSignature Off这样 HTTP 响应头里只会显示Server: Apache,不会暴露具体的版本号和操作系统信息。其次是禁用 TRACE 方法:
TraceEnable OffTRACE 方法在某些情况下可能被用于跨站追踪攻击,直接禁掉最省心。
实操心得:每次修改配置文件后,不要直接
restart,而是先用httpd -t检查语法,然后用apachectl graceful平滑重启。graceful会等待当前正在处理的请求完成后再重启,不会中断用户连接。只有graceful失败的时候才用restart。
6. 版本升级与模块扩展的实操建议
6.1 小版本升级的正确姿势
Apache 的小版本升级(比如从 2.4.57 升到 2.4.58)通常只是修 bug 和安全补丁,编译参数和配置文件基本不用改。升级流程可以总结为“编译新版本、替换二进制、保留配置”。
具体操作:先下载新版本源码,用和之前完全相同的configure参数编译安装到一个临时目录,比如/opt/apache-new。然后停掉旧服务,把旧目录改名备份,把新目录改名为/opt/apache,再把备份目录里的conf和logs复制回来。最后启动服务,用httpd -v确认版本号。
systemctl stop apache mv /opt/apache /opt/apache-old mv /opt/apache-new /opt/apache cp -a /opt/apache-old/conf/* /opt/apache/conf/ cp -a /opt/apache-old/logs/* /opt/apache/logs/ systemctl start apache /opt/apache/bin/httpd -v这种方式的优点是回滚极其简单——如果新版本有问题,停掉服务,把目录名换回来就行。
6.2 动态添加新模块
前面说了--enable-so的好处是支持动态加载模块。如果你后来发现需要某个编译时没启用的模块,比如mod_ldap,不需要重新编译整个 Apache,只需要单独编译这个模块然后加载即可。
Apache 源码包里有一个apxs工具(在/opt/apache/bin/apxs),可以用它来编译第三方模块或者源码包里自带的模块。以mod_ldap为例:
cd /usr/local/src/httpd-2.4.58/modules/ldap /opt/apache/bin/apxs -i -a -c mod_ldap.c-i表示安装模块,-a表示自动在httpd.conf里添加LoadModule指令,-c表示编译。执行完之后,mod_ldap.so会被复制到/opt/apache/modules/目录,并且httpd.conf里会自动加上对应的LoadModule行。
但要注意,apxs编译模块时需要对应的开发库。比如mod_ldap需要openldap-devel,mod_session需要libcurl-devel。如果编译时报错说找不到头文件,先确认对应的开发包是否安装。
6.3 编译参数变更后的处理
有时候你不得不改变编译参数,比如原来没启用 HTTP/2,现在需要了。这种情况下不能简单地替换二进制文件,因为新编译的 Apache 可能依赖新的库,或者模块的 ABI 发生了变化。
正确的做法是:用新的configure参数完整编译安装到一个新目录,然后对比新旧目录的差异。重点检查modules目录下多了哪些.so文件,conf目录下的httpd.conf有没有新增的配置项。把新增的LoadModule指令合并到你的配置文件里,然后切换目录。
如果新旧版本的模块 ABI 不兼容(比如从 2.4.5x 升到 2.4.6x 时某些模块的接口变了),那就不能混用新旧模块,必须全部用新编译的版本。这也是为什么我建议每次升级都完整编译,而不是只替换主程序。
最后分享一个小技巧:在编译之前,用
./configure --help看一下所有可用的参数。Apache 的configure脚本支持很多细粒度的控制,比如--enable-mods-shared=most可以一次性把所有常用模块编译成动态模块,省去逐个--enable的麻烦。但这样编译出来的模块比较多,需要你在配置文件里手动决定加载哪些。对于新手来说,我建议还是逐个--enable,这样你对每个模块的作用和依赖关系会更清楚。