Apache启用HTTPS后,泛域名证书的配置核心仍然是SSL引擎和证书路径,但在实际生产环境里,很多HTTPS握手失败并不是因为证书本身无效,而是证书链不完整、虚拟主机选择顺序错误或者TLS协议设置过旧。尤其是泛域名证书会和多个子域名共用同一张证书,如果某一个虚拟主机没有正确命中SNI,客户端就可能拿到错误的证书,从而触发证书名称不匹配的告警。

Apache加载通配符证书的基础配置
在开始配置前,需要确认Apache已经启用了mod_ssl模块。Debian或Ubuntu系统可以使用a2enmod ssl命令启用模块,CentOS或Rocky Linux则需要确认httpd配置里包含LoadModule ssl_module modules/mod_ssl.so这一行。模块加载完成后,才能使用SSLEngine、SSLCertificateFile等SSL相关指令。
通配符证书的文件通常由两部分组成:证书文件和私钥文件。证书文件可能是单独的叶证书,也可能已经包含中间证书,也就是常说的fullchain文件。Apache 2.4.8之后,SSLCertificateChainFile指令已经废弃,官方推荐的做法是把中间证书和叶证书合并到同一个文件,再用SSLCertificateFile指定这个完整链文件。如果服务端只发送叶证书而不发送中间证书,Android手机、旧版iOS以及部分Java客户端就无法构建可信链。
下面是一个标准的443虚拟主机配置。ServerAlias中可以列出所有需要被该证书覆盖的一级子域名,例如www.ipipp.com、api.ipipp.com、cdn.ipipp.com等。
<VirtualHost *:443>
ServerName ipipp.com
ServerAlias www.ipipp.com api.ipipp.com cdn.ipipp.com
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /etc/ssl/certs/ipipp.com.fullchain.pem
SSLCertificateKeyFile /etc/ssl/private/ipipp.com.key
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>
</VirtualHost>这里的SSLCertificateFile指向fullchain.pem文件,其中依次包含服务器证书和中间证书。私钥文件必须单独存放,权限建议设置为600,所在目录权限设置为700,防止其他系统用户读取私钥内容。
SNI与多虚拟主机匹配规则
当同一台Apache服务器同时运行多个HTTPS站点时,Apache会根据客户端发送的SNI扩展来选择对应的虚拟主机。这意味着即使只有一个IP地址,也可以为不同的域名部署不同的证书。但如果客户端不支持SNI,Apache会返回默认虚拟主机的证书。默认虚拟主机通常是配置文件中第一个出现的443虚拟主机,因此把需要覆盖范围最广或最重要的虚拟主机放在最前面可以降低兼容性风险。
通配符证书的使用范围也需要注意。标准通配符证书*.ipipp.com只匹配一级子域,比如www.ipipp.com、api.ipipp.com,不会匹配a.b.ipipp.com这样的多级子域。如果业务中存在多级子域,要么单独申请多级通配证书,要么为这些多级子域单独配置证书。可以通过下面的命令查看证书实际包含的SAN名称,避免配置了不存在的域名却以为证书可以覆盖。
openssl x509 -in /etc/ssl/certs/ipipp.com.fullchain.pem -noout -text | grep -A1 "Subject Alternative Name"
如果输出结果中只有*.ipipp.com和ipipp.com,那么a.b.ipipp.com就不在覆盖范围内。此时如果虚拟主机中配置了ServerAlias a.b.ipipp.com,用户访问时会出现证书名称不匹配。解决方法是单独为多级子域申请证书,或者调整域名规划,把所有服务都放在一级子域下。
证书链完整性与TLS协议安全
证书链不完整是Apache配置通配符证书时最常见的坑。服务端发送证书时,需要把中间证书一并发送给客户端,客户端才能从叶证书逐级验证到根证书。如果只有叶证书,部分客户端会尝试下载中间证书,但很多客户端不会主动下载,导致验证失败。可以使用openssl s_client命令测试实际握手时服务端发送了哪些证书。
echo | openssl s_client -connect ipipp.com:443 -showcerts 2>/dev/null | grep "s:"
输出结果中应该至少包含叶证书和中间证书两行。如果只有一行,说明证书链没有正确加载。修复方式有两种:一是直接使用CA签发的fullchain文件;二是手动把中间证书追加到叶证书后面,比如执行cat certificate.crt intermediate.crt > fullchain.crt。合并后重新加载Apache即可。
TLS协议和加密套件的配置同样影响安全性和兼容性。建议禁用SSLv2、SSLv3、TLSv1和TLSv1.1,只保留TLSv1.2和TLSv1.3。加密套件优先选择前向安全的算法,避免使用MD5、RC4等弱算法。
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!MD5:!RC4:!3DES SSLHonorCipherOrder on
在支持OpenSSL 1.1.1及更高版本的系统上,Apache 2.4.36之后可以直接协商TLSv1.3。TLSv1.3的加密套件与TLSv1.2不同,Apache会自动处理,不需要在SSLCipherSuite中单独列出。配置完成后可以使用systemctl reload apache2或systemctl restart httpd让配置生效,然后通过浏览器锁形图标或openssl s_client确认协议版本和证书信息。
自动化申请与续期泛域名证书
泛域名证书无法通过HTTP验证方式从Let's Encrypt获取,必须使用DNS验证。常见的自动化工具acme.sh可以配合多个DNS服务商的API完成验证,整个过程无需手动添加TXT记录。以Cloudflare为例,先设置DNS API的密钥环境变量,然后执行issue命令申请证书。
export CF_Token="你的Cloudflare_API_Token" acme.sh --issue --dns dns_cf -d ipipp.com -d '*.ipipp.com'
这里通配符域名要用单引号包起来,防止shell把星号展开成当前目录文件名。申请成功后,acme.sh会把证书存放在自己的目录中。接下来需要把证书安装到Apache可以读取的路径,并设置reload命令让Apache自动加载新证书。
acme.sh --install-cert -d ipipp.com -d '*.ipipp.com' \ --key-file /etc/ssl/private/ipipp.com.key \ --fullchain-file /etc/ssl/certs/ipipp.com.fullchain.pem \ --reloadcmd "systemctl reload apache2"
acme.sh的续期是自动进行的,默认每60天检查一次。由于安装时已经指定了reload命令,证书更新后Apache会自动重载,不需要人工干预。需要注意的是,DNS API密钥要妥善保管,权限不要暴露在Web目录中。如果使用Certbot,配置思路类似,仍然需要DNS插件完成通配符证书的验证。
泛域名证书在Apache上的配置本质上并不复杂,但证书链、虚拟主机顺序、TLS版本这些细节决定了HTTPS是否能稳定工作。只要在部署时确认fullchain文件完整、ServerAlias与实际证书覆盖范围一致、SNI匹配正确,并配置好自动化续期,整套环境就能长期稳定运行。