
我最近帮朋友把一个企业官网从HTTP升级到HTTPS本来以为半小时能搞定的事结果在他那台服务器上足足折腾了一个下午。倒不是操作有多难而是二级域名这个“二等公民”在证书选型、DNS解析、nginx配置各个环节都有不少默认的坑。很多教程只讲主域名怎么配一到二级域名就语焉不详。这篇文章我把整套流程从头到尾拆开讲目标只有一个让你按着顺序做完二级域名也能稳稳地挂上绿锁。先说清楚本文适用对象手里有一个主域名想给类似blog.example.com、api.example.com这样的二级域名部署HTTPS的读者或者已经给主域名配过证书、但被“这个证书另一个域名能不能用”之类问题卡住的读者。我不讲太多理论尽量给可以直接上手的东西但每个关键步骤背后的原因也会说明白免得你照着配完出了问题不知道怎么改。1. 二级域名上HTTPS之前先搞清证书类型与DNS这两件大事很多人一上来就打开阿里云控制台申请证书结果卡在“该选择哪一类证书”上或者证书下来了发现根本不含二级域名然后就开始怀疑人生。其实这个阶段只需要想清楚两件事你的证书要覆盖哪些域名你的DNS解析能不能配合证书验证。这两件事想明白了后面全是机械操作。1.1 单域名证书、SAN证书、泛域名证书到底怎么选SSL证书覆盖域名的规则是初学者最容易搞混的地方。先说结论一张普通的单域名证书比如example.com它只保护这一个域名不能同时保护www.example.com也不能保护blog.example.com。哪怕后缀完全一样浏览器也严格按证书里写的域名来校验多一个字符、少一个字符都不行。如果你需要保护的不止一个域名有两条路多域名证书SAN证书一张证书里可以同时写入多个域名比如example.comwww.example.comblog.example.com。域名多的时候按“域名数量”收费但灵活性很好适合域名数量确定、不打算乱加子域的情况。泛域名证书Wildcard证书证书里写*.example.com可以保护blog.example.com、api.example.com、shop.example.com等所有二级域名但一般不包含主域名example.com本身。如果你确定以后会新增二级域名泛域名证书是最省心的加一个子域不用重新申请证书。要注意泛域名只能匹配一级子域a.b.example.com这种三级域名它是管不到的。我自己的经验是主域名加得勤、子域又多的人直接上泛域名就三五台服务器、域名固定不变多域名SAN证书就够了只给一个博客子域用那就老实申请单域名证书。免费方案里阿里的免费证书大多只能给一个域名用Lets Encrypt则可以用acme.sh同时签发多域名和泛域名后面会细说。注意区分“证书覆盖域名”和“服务器上可以配置多个HTTPS站点”这两个概念。nginx可以在一台服务器上挂无数张证书每个server块用不同的证书互不影响。你完全没必要为了两个二级域名非去凑一张泛域名证书分开申请也完全可以。1.2 DNS解析最容易埋雷的地方A记录、CNAME与验证冲突证书申请之后无论是文件验证还是DNS验证本质上都是要在你的域名上放一个“我是域名主人”的证明让CA按你提供的解析记录去查。这个环节新手最容易踩的坑如下。第一A记录指向必须生效。申请证书前先把二级域名的DNS解析做好例如blog.example.com用A记录指向服务器IP。很多人习惯用CNAME解析到主域这在普通HTTP访问时没问题但部分CA在做DNS验证时对CNAME会有额外的要求一旦验证记录查不到证书签发就会一直卡在“验证中”。我建议二级域名的业务解析直接用A记录省事。第二DNS验证记录别急着删。很多人在阿里云或者Cloudflare的后台添加了一条_dnsauthTXT记录证书签发之后立刻把它删掉。这个习惯不好因为免费证书常常没有自动续期你下次申请同一域名时又要加同样的记录不如让验证记录留着不影响业务解析。另外记录一旦删除某些CA的证书吊销检查如果引用它可能出意外。第三泛解析和具体解析冲突。如果你的主域名做了*.example.com泛解析有些CA的DNS验证会读取到泛解析的TXT记录导致验证失败。这种情况通常发生在域名同时在多个平台做解析时。解决办法是在做证书验证时单独把要验证的域名解析到托管商的控制台里操作不要在CDN层面做泛解析。1.3 免费证书每年一次的续期游戏阿里云与Lets Encrypt的取舍关于“阿里云ssl证书免费续期”我实际体验下来发现很多人对这个词有误解。阿里云的免费DV证书确实可以每年重新申请一次但“免费续期”不等于“自动续期”它通常是你手动提交新申请、重新走一遍验证流程然后下载新证书替换到服务器上。前几年阿里云的免费证书是1年有效期后来政策调整过不同时间段申请的有效期不完全一样以你控制台申请时显示的时间为准。阿里云也提供过3个月周期的免费证书这操作对个人用户而言换证书频率会高一倍如果你不想频繁折腾可以考虑用Lets Encrypt。Lets Encrypt的证书有效期是90天听起来比阿里云的免费证书短但它有自动续期机制配置好之后让cron定期执行证书到期前自动换新。对服务器管理员来说前期花10分钟配置自动化之后就不用管了。这个方案的实现方式主要是acme.sh或者certbot我在后面的章节会专门给出一套跑通的流程。个人建议如果只有一台服务器、域名不多用Lets Encrypt全家桶完成签发、续期、部署如果公司里要求有“正规厂商证书”不习惯命令行操作就在阿里云控制台做申请但一定要设日历提醒到期前去换。别等到证书过期、访客浏览器一片红时才发现。2. 证书申请与私钥落地拿到文件之后别急着传服务器证书申请这一步不同的CA界面不一样但核心产出的东西是一样的一张证书、一个私钥、一个证书链。很多人卡在申请完不知道怎么把文件从Windows弄到服务器上或者解压出来看到一堆后缀名头昏眼花。这一节就把这些常被忽略的细节说透。2.1 阿里云免费证书申请流程中容易被忽略的细节在阿里云控制台搜索“数字证书管理服务”进入SSL证书申请页面选择免费证书填你的二级域名比如blog.example.com。验证方式建议选DNS验证因为文件验证需要你在服务器web根目录放一个指定内容的文件而有些人的站点是前后端分离的nginx根目录指到静态资源目录找不到对应路径。提交申请之后控制台会给你一条TXT记录格式大概像这样主机记录_dnsauth.blog.example.com 记录值202503xxxxxxx把它加到你的DNS解析平台等CA验证通过证书状态变成“已签发”就可以下载了。这里有个细节阿里云签发的证书压缩包解压后通常包含nginx、apache、iis、tomcat几个目录别选错了——nginx目录里只有.pem和.key两个文件这正是我们需要的。证书文件下载完建议核对一下证书内容里的域名信息openssl x509 -in blog.example.com.pem -noout -subject -issuer -dates看到subject里包含你的完整域名、dates里的起止时间合理再往服务器上传。检查这一步虽然简单但能避免很多“配置半天发现文件不匹配”的尴尬。2.2 文件传输与格式识别PEM、KEY、CRT、PFX到底谁是谁搞懂证书文件的格式后半程你会非常顺畅。我们阿里云拿到的.pem证书就是证书内容本身以-----BEGIN CERTIFICATE-----开头和.key私钥以-----BEGIN PRIVATE KEY-----开头。.crt和.pem内容本质上是一回事只是扩展名不同。.pfx/.p12是带私钥的打包格式主要在Windows IIS上用nginx里用不到。文件传服务器时注意两点一是用SFTP或者scp工具不要把证书内容直接贴到聊天软件里中转私钥一旦泄露就等于把域名的HTTPS信任拱手让人二是拿到服务器之后证书和私钥建议放在/etc/nginx/ssl/或者/etc/ssl/private/这类目录下并且设置合理权限mkdir -p /etc/nginx/ssl chown root:root /etc/nginx/ssl chmod 600 /etc/nginx/ssl/blog.example.com.key chmod 644 /etc/nginx/ssl/blog.example.com.pem私钥文件权限一定要收紧到只有root能读写证书本身因为是公开内容权限可以宽松一点。有些发行版默认 nginx worker 进程以nginx用户运行如果私钥权限过严比如400且所有者是rootnginx启动时可能因无法读取私钥而直接报错这个细节在高版本nginx里比较常见如果你遇到“permission denied”多半就是这里。2.3 用acme.sh申请Lets Encrypt证书的一条龙操作Lets Encrypt的自动化方案里我比较推荐acme.sh脚本轻量对DNS解析平台的支持也全。以阿里云DNS为例先在控制台生成一组AccessKey然后执行curl https://get.acme.sh | sh -s email你的邮箱 export Ali_Key你的AccessKey ID export Ali_Secret你的AccessKey Secret acme.sh --issue --dns dns_ali -d example.com -d *.example.com这里-d example.com -d *.example.com可以只签发一次让证书同时覆盖主域名和所有二级域名效果等同于泛域名证书算是性价比很高的用法。脚本默认用DNS API自动添加TXT记录签发完成后证书文件会保存到~/.acme.sh/example.com/目录。注意这个目录下的文件是中间产物不要直接引用到nginx配置里要用脚本提供的--install-cert把证书和私钥安装到指定目录acme.sh --install-cert -d example.com -d *.example.com \ --key-file /etc/nginx/ssl/example.com.key \ --fullchain-file /etc/nginx/ssl/example.com.pem \ --reloadcmd systemctl reload nginxacme.sh会自带一个cron任务之后每次到期前自动续期、自动reload nginx这才是真正的“免费续期自动化”。我第一次配完这个之后一年多没再手动碰过证书文件到期自动换新省心得很。3. nginx同时服务主域名和二级域名server块与证书路径的配合证书文件已经就位接下来就是nginx配置。这个环节要理解的核心是nginx对每个域名、每个端口都可能有一个独立的server块HTTPS配置就是在对应server块里加上listen 443 ssl并指定证书路径。主域名和二级域名同时跑在一台服务器上时server块的写法有一些讲究下面我按几种常见的场景分别给出配置。3.1 最基本的双server块写法主域名一个块二级域名一个块假设服务器上同时要跑example.com和blog.example.com最简单直接的方案就是写两个独立的server块每个块指定自己的证书server { listen 443 ssl; server_name example.com www.example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; root /var/www/html; index index.html; } server { listen 443 ssl; server_name blog.example.com; ssl_certificate /etc/nginx/ssl/blog.example.com.pem; ssl_certificate_key /etc/nginx/ssl/blog.example.com.key; root /var/www/blog; index index.html; }注意一点主域名尽量把example.com和www.example.com写在同一个server块里。如果你干脆没给www申请过证书那可以把www.example.com单独跳转到不带www的主域或者干脆不带www直接访问。很多人的线上问题就出在证书只覆盖了example.com结果www访问时浏览器报证书不匹配。接着在/etc/nginx/nginx.conf的http块里确保包含站点配置文件目录include /etc/nginx/conf.d/*.conf;然后把上面两个server块放在/etc/nginx/conf.d/example.conf或者/etc/nginx/sites-available/下看发行版习惯。配置完成后执行nginx -t systemctl reload nginxnginx -t会检查语法和证书文件能否正常读取这一步如果爆出cannot load certificate之类的错误优先去检查证书路径和权限。3.2 一张证书同时覆盖多个二级域名时的配置写法如果你用acme.sh签发了*.example.com泛域名证书那所有二级域名可以共用同一个证书文件配置会简化不少server { listen 443 ssl; server_name blog.example.com api.example.com shop.example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; # 后面根据请求域名区分不同应用可以在这里用 if 或者 proxy_pass 配合变量 }当多个域名共用一个证书时nginx只认证书里的域名列表server_name写几个域名都可以。这个场景最常出现在“一套反向代理服务多个二级域名”的结构里一个入口根据请求的Host转发到不同后端服务。配置反代时可以这样写server { listen 443 ssl; server_name api.example.com blog.example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; } }这里有一个点经常被忽略有时候你只需要配置一个二级域名但是证书用的是泛域名证书你可以在server_name里只写一个子域同样可以生效。证书的覆盖范围和server块监听范围是两回事。3.3 HTTP强制跳转HTTPS不要用return 301写死配置完443接下来要处理80端口的跳转。最常见的写法是server { listen 80; server_name blog.example.com; return 301 https://$host$request_uri; }这里的$host是请求头里的Host能自动带上用户实际访问的域名比你写死https://blog.example.com$request_uri更稳妥。之所以建议用$host是因为在泛域名、多域名共用配置的场景下写死域名会导致其他域名访问时被跳到错误地址。另外如果你后期改了域名$host方案也不用改跳转配置。还有一个问题值得注意如果你用WordPress之类的CMSHTTP跳转之后还会出现资源加载问题。具体表现是首页能打开但浏览器地址栏没有出现绿锁打开开发者工具发现页面里有很多http://开头的图片、CSS、JS资源。这不是nginx配置的问题而是站点后台的“站点地址”还写着HTTP链接。解决办法是在站点配置文件或者WordPress设置里把站点地址改成HTTPS同时把数据库里旧的HTTP站内链接批量替换掉。我第一次部署完就是卡在这一步首页虽然返回了HTTPS但引用的静态资源全是HTTP浏览器照样报不安全。4. 配置完成后躲不开的验证与排错配置写完、nginx也reload了可千万别急着收工。HTTPS上线最怕的就是“当时看着正常第二天用户反馈打不开”。规则很简单所有排查要在命令行里完成不要只盯着浏览器看。下面给出我自己最常用的一整套验证与排错路径顺便把CVE-2005-4900那段老配置的往事也说一下。4.1 用curl和openssl验证证书链与到期时间配置完第一件事先看本机加跳转是否符合预期curl -I https://blog.example.com正常返回结果是HTTP/2 200或者HTTP/1.1 200 OK。然后看证书本身对不对echo | openssl s_client -connect blog.example.com:443 -servername blog.example.com 2/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName这条命令会输出证书的持有者、签发者、有效期和扩展的San域名列表。重点看两处一是subjectAltName里是否有DNS:blog.example.com二是dates里的notAfter是不是还在有效期内。如果subjectAltName里没有当前访问的域名浏览器一定会报错而且这个错误在nginx日志里往往不明显全靠openssl查出真相。再检查证书链是否完整openssl s_client -connect blog.example.com:443 -showcerts 2/dev/null | grep BEGIN CERTIFICATE正常情况下会有两张或者三张证书服务器证书 中间CA证书 根证书有时会省掉。如果只出来一张证书大概率是你在nginx配置里用的ssl_certificate指定了只有证书内容的cert.pem而没带证书链的fullchain.pem。用acme.sh安装时一定要选fullchain-file用阿里云下载的压缩包则要看清楚里面是否配有chain文件没有的话把CA给你的中间证书内容直接拼接在证书文件后面再引用也能解决。4.2 SSL证书导致的浏览器报错快速对照表线上的HTTPS报错信息五花八门我整理了几个最高频的方便你照着排查报错现象可能原因排查方向NET::ERR_CERT_COMMON_NAME_INVALID证书域名和访问域名不匹配openssl看subjectAltNameSSL_ERROR_BAD_CERT_DOMAINFirefox同上同上NET::ERR_CERT_AUTHORITY_INVALID证书链不完整或用了自签证书检查fullchainERR_SSL_OLD_VERSION_INTEROPERABILITY浏览器太老或ssl_protocols配置过低升级协议配置SSL: error:0A000412 in nginx日志私钥和证书不匹配用openssl比对公钥Permission denied读取.key证书文件权限问题调整到600补充一个私钥和证书是否匹配的快速判定法# 分别提取证书和私钥的公钥指纹 openssl x509 -in cert.pem -noout -pubkey | openssl md5 openssl pkey -in key.pem -pubout | openssl md5两个输出的md5值一致说明证书和私钥配对正确。我之前就遇到过证书是两个月前申请的、私钥是昨天重新生成的结果两边指纹对不上在浏览器里表现为握手直接失败日志里报错是ssl handshake failed排查起来相当绕。4.3 弱哈希算法(CVE-2005-4900)那段老配置的往事在热搜词里看到“win下ssl证书使用了弱hash算法(cve-2005-4900)对应的补丁名称”我一下子想起前几年排查过的一个老旧环境。CVE-2005-4900说的是证书签名算法使用了MD2这种老旧哈希攻击者可以伪造证书导致客户端无法信任。这个问题集中出现在Windows Server 2003、2008时期的自签证书或者老CA签发的证书上。实际运维中遇到这个报错建议不要纠结“对应的补丁名称是多少”因为微软官方对应此漏洞的补丁早在多年前的月度更新里就已经固化你要做的不是去打补丁而是彻底换掉旧证书。解决路径是重新生成证书注意签名算法要用SHA-256不要用SHA-1更不要用MD5openssl req -new -newkey rsa:2048 -nodes -out example.csr -keyout example.key -sha256如果是内部测试环境需要自签证书也务必加-sha256。自签证书在OV/EV等级上没有太多讲究但哈希算法一定要用SHA-256否则在较新的操作系统和浏览器里会直接报“证书使用了弱签名算法”。这个教训的普遍意义是HTTPS配置里协议版本和签名算法是两码事TLS版本再新、证书签名算法太老一样不被信任。4.4 常见网络排查telnet端口与防火墙别忽略证书配置正常但HTTPS还是打不开时先别怀疑证书从最基本的端口连通性查起telnet 服务器IP 443如果telnet卡住或者connection refused说明443端口根本不通可能是防火墙或安全组没放行。阿里云服务器默认需要同时在“安全组规则”和系统防火墙iptables/firewalld里放行443端口两者都配置好才能外面访问。每次我给客户排查HTTPS问题至少有一半是卡在这nginx监听443了但安全组没加规则外面连不进来。放行端口的命令因系统而异CentOS常见的firewall-cmd --permanent --add-servicehttps firewall-cmd --reloadUbuntu上可能是ufw allow https。这类环境问题解决后再回头检查证书也不迟。5. 上线跑一阵之后的几点体会最后分享几条实际运维下来的心得不算什么高深理论但都是我交过学费换来的。第一证书到期监控一定要做。Lets Encrypt自动续期也不是100%可靠遇到过服务器磁盘满、cron被清理、DNS API密钥过期导致续期失败的情况。我现在的做法是在服务器上写一个简单脚本每天检查证书剩余天数不足30天就往企业微信/钉钉群里发提醒。脚本逻辑很直白expire_date$(openssl x509 -enddate -noout -in /etc/nginx/ssl/example.com.pem | cut -d -f2) expire_days$(( ($(date -d $expire_date %s) - $(date %s)) / 86400 )) if [ $expire_days -lt 30 ]; then # 发送告警 fi这套东西不复杂但关键时刻真能救命。第二配置完HTTPS后别忽略性能。很多人以为上线HTTPS就完事大吉其实TLS握手是有额外开销的。建议在你的443 server块里加上TLS协议和会话复用配置ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_stapling on; ssl_stapling_verify on;sesssion_cache和ssl_stapling这两个参数一个减少重复握手的开销一个加快证书链验证速度用户体感上的首屏时间差距很明显。我在一台配置不高的云服务器上做过对比开启会话缓存后重复页面请求的握手时间几乎可以忽略。第三如果以后你想再增加一个新的二级域名不要从零开始走一遍流程。只要你的证书是泛域名证书新加一个子域只需要在DNS加一条A记录指向服务器、在nginx里加一个server块、然后nginx -t systemctl reload nginx三分钟搞定。如果不是泛域名那就得重新申请证书但申请流程也就是重复第一、二章节的过程建议把申请时用到的DNS解析账号、控制台入口、服务器路径这些信息记到一张文档里避免半年之后“当初我怎么申请的都忘了”这种尴尬。二级域名配HTTPS这件事本质上就是一个“证书覆盖域名 正确加载证书 正确转发请求”的组合题。把证书类型选对、DNS解析做爽、server块写清楚、验证排错走一遍之后就是享受绿锁带来的信任感和访客安全感了。希望这篇文章能让你少走几步弯路一次配通。