跳到主要内容

泛域名 HTTPS 解析实践:一张通配证书,Nginx 按 host 分发所有子站点

· 阅读需 7 分钟

一台服务器上跑着博客、网盘、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 写了通配符,且没有精确块抢先匹配
新子域名 404map $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 加一行,证书零成本、零手工


参考