泛域名 HTTPS 解析实践:一张通配证书,Nginx 按 host 分发所有子站点
一台服务器上跑着博客、网盘、GitLab、漫画站……每个都想有自己的子域名(blog.、git.、nas.),每个子域名都想走 HTTPS。最省事的做法是:一张泛域名证书 + 一个泛域名 server 块,接收所有子域名,再按 host 分发到各自的服务。
1. 场景:一个域名,N 个子服务
需求是这样的:
两种实现对比:
| 方案 | 证书数量 | server 块 | 新增子域名 |
|---|---|---|---|
| 每域名单独签 | N 张 | N 个 | 要重新签证书 + 加配置 |
| 泛域名证书 | 1 张 | 1 个泛块 + 需要时加精确块 | DNS 加记录 + map 加一行 |
泛域名方案的核心收益:证书只管一次,新子域名零证书成本。
2. 两个"泛"叠加:DNS 泛解析 + 通配证书
"泛域名 HTTPS"其实是两件事叠加:
① DNS 泛解析:加一条 *.example.com 的 A 记录,让任意子域名都解析到这台服务器。这样不管用户访问 blog.、git. 还是任何新子域名,流量都先到 nginx。
② 通配证书:*.example.com 证书覆盖所有一级子域名(通常同时带根域名 example.com,因为通配符只匹配一级,不匹配根本身)。
为什么通配证书必须走 DNS-01 验证
普通证书常用 HTTP-01 验证(在 /.well-known/acme-challenge/ 放一个文件)。但通配证书不能走 HTTP-01——因为你没法给"还没建立的所有子域名"都放文件。通配证书只能用 DNS-01 验证:
你 → 向 CA 证明"我拥有 example.com" → 在 DNS 里添加一条 TXT 记录
CA → 查询该 TXT 记录 → 匹配 → 签发 *.example.com 证书
手动做 DNS-01 很痛苦(每次续签都要手动加 TXT)。解法是让 ACME 客户端调用 DNS 服务商的 API 自动增删 TXT 记录——这就是后面 acme.sh + 腾讯云 DNSPod 的配合。
3. Nginx 配置:一张证书接收所有子域名
3.1 泛域名 server 块(兜底一切子域名)
# 泛域名块:接收所有 example.com 子域名的 HTTPS 请求
server {
listen 443 ssl;
server_name example.com *.example.com; # 根域名 + 泛域名
ssl_certificate /etc/nginx/ssl/fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
root $site_root; # 根目录由 $site_root 决定
location / {
try_files $uri $uri/ =404;
}
}
3.2 按 host 分发:map $host $site_root
一个泛域名块怎么知道"blog. 该用博客目录、nas. 该用网盘目录"?——nginx 的 map 指令按 host 变量映射:
# nginx.conf 里、include conf.d 之前定义(供各站点引用)
map $host $site_root {
default /var/www/example/www; # 兜底:没写明的子域名
demo.example.com /data/www/demo;
}
于是"一个证书 + 一个泛块 + 一个 map"就覆盖了所有子域名,新增子域名只需:DNS 加记录 + map 加一行。
3.3 精确 server 块优先于泛域名块
有些子域名需要特殊处理(如博客要配 404 页、某些站要反代到后端)。nginx 匹配 server_name 的规则是:精确匹配 > 泛域名匹配。所以给 blog. 写一个精确 server 块,它就会优先命中,泛域名块不会抢:
# 精确块:blog.example.com(优先于 *.example.com 泛块)
server {
listen 443 ssl;
server_name blog.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
root /home/blog/repo/hexo;
error_page 404 /404.html;
}
3.4 静态站与反代站并存
同一套证书体系下,既可以有纯静态站(root + try_files),也可以有反代站(proxy_pass 到本机端口):
# www.example.com:反代到本机服务
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:8889;
}
location /trend {
proxy_pass http://127.0.0.1:6666;
}
}
注意:
127.0.0.1:8889等是本机内部端口,反代场景下每个服务仍各自独立运行,nginx 只做 443/80 的统一入口。
4. 签发与自动续签:acme.sh + 腾讯云 DNS
4.1 流程
要点:
- CA 用 ZeroSSL(acme.sh 默认),证书 90 天有效;
- DNS 验证走腾讯云 API:acme.sh 的
dns_tencent适配脚本自动在 DNSPod 加/删 TXT; - 全程无人值守:acme.sh 每天跑一次定时任务,到期前 30 天内自动续签 + 重载 nginx。
4.2 关键命令
# 1. 设置腾讯云 API 密钥(只授权 DNS 解析权限的子账号)
export Tencent_SecretId="你的SecretId"
export Tencent_SecretKey="你的SecretKey"
# 2. 签发泛域名证书(根域名放前面,泛域名带引号)
acme.sh --issue --dns dns_tencent -d example.com -d '*.example.com'
# 3. 部署到 nginx(不要引用 ~/.acme.sh 内部目录)
mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.key \
--fullchain-file /etc/nginx/ssl/fullchain.crt \
--reloadcmd "nginx -s reload"
安全提醒:
SecretKey不要写进任何 git 仓库,只在服务器上设置;私钥/etc/nginx/ssl/example.key记得chmod 600并备份。
4.3 ZeroSSL 的额度要注意
ZeroSSL 免费版每 30 天最多 3 个新证书(含强制重签)。正常自动续签不受影响,但别频繁 --force 重签,否则额度会用光。
5. 维护与排错
# 巡检:证书还剩多少天
openssl x509 -in /etc/nginx/ssl/fullchain.crt -noout -enddate
# 证书列表
acme.sh --list
# 确认自动续签定时任务在跑
crontab -l | grep acme
常见问题:
| 问题 | 排查 |
|---|---|
| 子域名访问不了 HTTPS | 先确认 DNS 泛解析有没有生效(dig *.example.com) |
| 证书没续上 | 看 ~/.acme.sh/acme.sh.log;最常见是 SecretKey 失效/子账号没 DNS 权限 |
| 泛域名块没生效 | 确认 server_name *.example.com 写了通配符,且没有精确块抢先匹配 |
| 新子域名 404 | map $host $site_root 里加一行,或确认默认目录兜底 |
| ZeroSSL 额度用完 | 等 30 天,或临时 --set-default-ca --server letsencrypt 换 CA |
6. 一句话总结
泛域名 HTTPS 是"两个泛"的叠加:DNS 加一条
*.example.com的 A 记录让所有子域名指向服务器,再用一张*.example.com通配证书覆盖所有子域名。通配证书只能走 DNS-01 验证,所以用 acme.sh + 腾讯云 DNSPod API 自动增删 TXT 实现无人值守签发与续签。nginx 侧用一个泛域名 server 块 +map $host $site_root接收并分发所有子域名,精确块优先于泛块,静态站和反代站共用同一套证书。结果是:新加一个子域名 = DNS 加记录 + map 加一行,证书零成本、零手工。
