ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

本地Ubuntu申请SSL证书迁移到云服务器,nginx配置与自动续期实战指南

本地Ubuntu申请SSL证书迁移到云服务器,nginx配置与自动续期实战指南 在本地Ubuntu上申请好证书再把证书放到云服务器上用这活儿听起来简单但真的操作过的人都知道坑全藏在细节里。我自己踩过几次之后把整个流程彻底捋了一遍从申请方式选择、证书文件迁移、nginx配置到续期自动化完整记录下来希望能帮到有同样需求的人。不管你是家里有台Ubuntu机器、想给云上的站点配上HTTPS还是本来就在本地管理域名和证书、只是想把服务迁到云上这篇文章都值得看完。1. 整体思路为什么证书能在本地申请、云端使用很多朋友看到“在本地Ubuntu申请的证书放到云服务上”这个需求第一反应是证书不是跟着域名和服务器走的吗换台机器还能用这里要先把SSL证书的底层逻辑讲透不然后面操作起来心里没底。1.1 证书的本质是一对密钥不是“绑定在某台机器上”SSL证书尤其是像Lets Encrypt签发的证书本质上是一个“公钥 身份信息 CA签名”的文件组合配套的还有一个私钥文件。公钥和私钥是成对出现的整个HTTPS握手过程中服务器向客户端出示证书然后用自己的私钥完成一次签名验证证明“我确实持有这个证书对应的私钥”。换句话说证书并不绑定某台服务器的IP或主机名它绑定的是“域名 私钥”。只要域名没变、私钥没丢把证书文件和私钥文件整体搬到另一台机器上这台机器就能继续用同一个HTTPS身份。这就好比身份证上的信息写的是你本人你把身份证和本人一起搬到另一个城市身份证依然有效并不需要在新的城市重新办一张。好多人迁移证书失败恰恰是因为只拷贝了证书文件、忘带私钥或者把私钥丢了然后又去重新申请。其实只要原来的私钥还在本地申请好的证书完全可以平移使用。1.2 为什么会有“本地申请、云端部署”这种需求我见的比较多的场景有三种。第一种域名解析在家里的公网IP上或者本地专门跑了一套证书管理服务。家里那台Ubuntu机器本身就是ACME客户端的主控端申请和续期都在它上面跑云服务器只是后来加的前端节点需要共享同一份证书。第二种本地没有对公网开放的80端口或者不想把端口暴露出去于是用DNS验证方式在本地申请。证书申请下来后再手动部署到云端。这种情况下证书的“出生地”在本地用户实际访问的却可能是云端就不得不做迁移。第三种企业内部有自己的CA体系或者用的是自签名证书签发工具只在某台内网Ubuntu机器上装了云端服务器是单独买的机器两边没有统一的证书管理平台最省事的方式就是从本地导出再导入。了解这些场景后你会发现这个流程并不是纯粹的“闲得没事折腾”它背后对应着真实的网络拓扑和证书管理现状。1.3 为什么不能“复制粘贴”了事很多人以为把证书文件scp到云端、填进nginx就能跑结果经常遇到“浏览器显示证书链不完整”“iOS设备直接不信任”“curl报错”等现象。原因在于部署证书不是只丢一个cert.pem过去那么简单。证书文件本身有好几个服务器证书、中间证书、完整证书链、私钥。这四者的关系我后面会详细拆。部署时要搞清楚哪个文件该放在哪个配置项里私钥权限不能太开放证书目录的属主和权限也要修正。而且Lets Encrypt证书默认有效期只有90天本地机器上的自动续期不会自动带动云端需不需要做同步、怎么做同步这些都是“复制粘贴”解决不了的。2. 申请证书前的准备与验证方式选择在动手申请之前先想清楚一个问题你的域名到底用哪种方式验证所有权这一步直接决定了你能不能顺利在本地申请到证书。2.1 环境准备安装certbot并确认域名解析我习惯用certbot它是Lets Encrypt官方推荐的ACME客户端。在本地Ubuntu上安装很简单sudo apt update sudo apt install certbot装完之后先确认域名能解析到你预期的地址。这一步很关键很多人申请失败不是certbot的问题而是域名解析还没生效。dig short example.com如果你用的是DNS验证域名解析指向哪里其实不一定重要因为验证过程靠的是DNS记录。但如果你打算用HTTP验证那域名解析必须能到达一台能访问到80端口的机器。申请之前把这些都确认好后面能省掉大量排查时间。2.2 三种验证方式怎么选ACME验证方式主要有三种每种都有各自的适用场景。第一种是standalone方式。certbot会临时起一个监听80端口的服务ACME服务器会通过你的域名访问这个端口来验证。这台机器必须公网可达、80端口没被占用。命令大概是sudo certbot certonly --standalone -d example.com --email youexample.com --agree-tos --no-eff-email第二种是webroot方式。certbot会在你的网站目录里放一个临时验证文件ACME服务器通过HTTP访问这个文件完成验证。这个适合nginx或apache已经在跑的场景但同样要求80端口能公网访问。第三种是DNS验证方式。certbot通过ACME的DNS-01 challenge让你在域名的DNS记录里添加一条TXT记录证明你控制这个域名。我比较推荐它作为“本地申请”的首选因为即使本地服务不暴露到公网、没有80端口只要你能改动DNS解析记录就能申请成功。手动方式是这样sudo certbot certonly --manual --preferred-challenges dns -d example.comcertbot会要求你登录DNS服务商创建一条指定的TXT记录然后回车继续。缺点是如果DNS服务商不支持API每次续期都要手动改比较麻烦。好在现在主流DNS服务商基本都有API配合certbot的DNS插件可以实现全自动验证。我补一句实践上的经验如果不是必须要签名到代码、或者申请的是通配符证书直接用DNS验证最省心。通配符证书因为域名前缀不确定目前只能用DNS验证这也是很多人在本地申请带*.example.com证书的唯一选择。2.3 生成的四个文件分别是什么申请成功后certbot会在/etc/letsencrypt/live/你的域名/目录下生成四个关键文件privkey.pem私钥绝不能泄露。cert.pem服务器证书本身。chain.pem中间证书也就是CA用来给服务器证书签名的那个证书。fullchain.pem服务器证书 中间证书的合并体。用一张表说清楚文件内容部署时放哪里privkey.pem私钥ssl_certificate_keycert.pem服务器证书一般不用chain.pem中间证书配合cert.pem某些场景用fullchain.pem完整证书链ssl_certificate强烈建议配置nginx或apache时用fullchain.pem而不是cert.pem。如果你只填cert.pem多数现代客户端会因为缺少中间证书而判定证书链不完整直接报错。3. 把证书从本地迁移到云服务并配置nginx这一节是整个流程的核心我会把每一步命令和原理都写清楚照着做基本不会出问题。3.1 在本地打包并传输证书先进入证书目录打包时需要带上完整证书链和私钥。我不建议打包整个/etc/letsencrypt因为里面还有账户信息、配置备份等和云端部署无关的内容带过去反而容易把目录结构搞乱。cd /etc/letsencrypt/live/example.com sudo tar czf ~/example.com-certs.tar.gz fullchain.pem privkey.pem然后通过scp把压缩包传到云服务器scp ~/example.com-certs.tar.gz useryour-cloud-server:~/这里有个安全提示privkey.pem是私钥传输全程必须走加密通道。scp本身走的是SSH加密没问题。但要特别提醒别图方便把证书压缩包扔到公开的网盘、代码仓库或者聊天工具里私钥一旦泄露你整个域名的HTTPS保护就形同虚设了。3.2 云上解压并修正权限在云服务器上执行sudo mkdir -p /etc/letsencrypt/live/example.com sudo tar xzf ~/example.com-certs.tar.gz -C /etc/letsencrypt/live/example.com然后必须修正权限。因为证书是从本地打包带过来的默认属主和权限可能不适合云上的nginx进程读取尤其是privkey.pem如果权限是644意味着任何用户都能读私钥这是非常危险的。sudo chown -R root:root /etc/letsencrypt/live/example.com sudo chmod 700 /etc/letsencrypt/live sudo chmod 755 /etc/letsencrypt/live/example.com sudo chmod 644 /etc/letsencrypt/live/example.com/fullchain.pem sudo chmod 600 /etc/letsencrypt/live/example.com/privkey.pem目录权限为什么要这样设/etc/letsencrypt/live设置为700普通用户进不去相当于把证书目录锁起来证书文件本身设置为644nginx master进程以root启动后子进程切换成nginx用户后仍能读取普通文件私钥设置成600只有root能读。这样既保证nginx能加载又尽量缩小了私钥暴露面。3.3 完整nginx配置示例与生效检查编辑nginx站点配置比如/etc/nginx/sites-available/example.comserver { listen 443 ssl; server_name example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name example.com; return 301 https://$host$request_uri; }写完后先测试配置再重载sudo nginx -t sudo systemctl reload nginxnginx -t会检查配置语法和证书路径能不能正常读取这一步输出syntax is ok后再reload。不要直接restart nginx生产环境下restart会短暂断开所有连接reload是平滑重载更稳妥。验证是否真正生效我通常不看浏览器而是用命令直接检查curl -v https://example.com 21 | grep -E subject:|issuer:或者用openssl手动检查证书链是否完整openssl s_client -connect example.com:443 -servername example.com -showcerts看到证书链里包含服务器证书和中间证书、并且最终信任才算部署成功。4. 常见问题与排查技巧实录这一节是我实际工作中踩坑最多的部分每个问题都值得单独拿出来讲。4.1 浏览器提示“证书不受信任”但curl又能用有一种很诡异的情况你用curl访问一切正常但手机浏览器或者一些老设备直接弹出“证书不受信任”。原因通常是证书链不完整。curl对证书链的要求相对宽松但浏览器更严格。如果nginx配的ssl_certificate只指向cert.pem很多客户端拿不到中间证书就会认为证书由未知CA签发。解决办法就是把配置改成fullchain.pem。我见过不少人在这上面卡了一天最后发现只是把文件填错了。还有一种情况你把证书从一个CA迁移过来中间证书名字、路径都和本地不一样结果云端只复制了cert.pem过去。记住迁移时最好直接打包fullchain.pem少走弯路。4.2 nginx替换证书不生效“我把新证书传上去了nginx也reload了怎么浏览器还是显示旧证书”这是高频问题。多数原因有三个第一你修改了证书文件但nginx没有真正重新加载。systemctl reload nginx有时候会因为语法检查失败而没有实际重载先跑nginx -t确认。第二浏览器端有OCSP缓存和会话缓存。新证书部署后浏览器会缓存旧证书信息。建议用隐身窗口或者换一个网络环境测试也可以用curl -v直接看服务器返回的证书这是最真实的。第三云服务器厂商自带的Web防火墙、负载均衡或者CDN节点缓存了旧证书。如果你是在阿里云ECS上用SLB/负载均衡做HTTPS证书要在负载均衡控制台里替换而不是只在后端ECS上改nginx。这个点特别值得注意很多人改了后端机器没改SLB自然不生效。4.3 云端续期失效Lets Encrypt证书有效期90天certbot默认会配置定时任务自动续期在本地机器上没问题。但你把证书迁到云上后云端并没有对应的certbot账户和自动续期任务所以90天后云端证书就过期了。解决思路是不要试图在云端单独申请一份新证书而是让本地继续作为证书签发方定时把最新证书同步到云端。具体做法我放在下一节详细展开。4.4 frp内网穿透场景下的证书到底放哪顺便讲一个大家容易混淆的场景如果你用frp这类内网穿透工具把本地Ubuntu的服务暴露到公网流量路径是“用户 - 云服务器 - 隧道 - 本地服务”。这时很多人误以为证书应该放在云服务器上其实要分情况。如果frp的请求是TCP穿透TLS终结发生在本地服务上那么证书必须放在本地。云服务器只是当一个流量转发器它根本不需要证书。如果你在云服务器上配了nginx做域名转发和HTTPS终结那证书才需要放到云上同时本地服务要改成HTTP回源。判断标准很简单TLS会话在哪里终止证书就在哪里。不要一股脑把证书都放云上反而可能导致本地服务不能正确启用HTTPS。5. 续期自动化让本地证书自动同步到云端最后这部分是升华也是我发现真正能帮大家省心的地方。证书部署不是一锤子买卖90天续期是常态手工操作迟早翻车。5.1 certbot renew 与 deploy-hookcertbot的续期命令是sudo certbot renew它只会续期到期时间不足30天的证书。如果续期成功会触发--deploy-hook指定的脚本。这个hook非常适合做证书同步因为只有证书真正更新了才会执行平时不会白白跑一遍同步逻辑。我推荐在本地写一个同步脚本/usr/local/bin/sync_certs_to_cloud.sh#!/bin/bash rsync -avz -e ssh /etc/letsencrypt/live/example.com/ usercloud-server:/etc/letsencrypt/live/example.com/ ssh usercloud-server chown -R root:root /etc/letsencrypt/live/example.com chmod 600 /etc/letsencrypt/live/example.com/privkey.pem systemctl reload nginx然后用sudo certbot renew --deploy-hook /usr/local/bin/sync_certs_to_cloud.sh测试一次。如果本地和云端的续期状态一致会自动跳过。5.2 配置定时任务certbot安装时默认会写一个systemd timer你也可以自己加cron确保每天尝试一次续期sudo crontab -e加入一行30 2 * * * certbot renew --quiet --deploy-hook /usr/local/bin/sync_certs_to_cloud.sh每天凌晨2点半跑一次只有到期前30天才会真正续期平时开销几乎为零。证书更新后自动同步到云端并reload nginx整个流程就全自动了。需要注意renew --quiet在没有需要续期的证书时不会输出任何信息日志排查时想看细节可以单独加--deploy-hook后面的日志输出比如重定向到文件。5.3 云上要不要也装certbot做双保险我个人的建议是既然选择了本地申请再上云就维持单一签发源不要让云端也去签一份证书。两个certbot同时运行各自维护一套证书文件迟早有一天会混淆。把云端当成一个“消费证书”的角色本地只负责签发和同步权责清晰排查问题也容易。当然如果你担心本地机器长期关机或者登录失效可以在云端留一个手动恢复包并存一份私钥备份到加密的离线存储里。但日常自动续期和同步靠本地就够了。这里再分享一个小技巧证书文件不要直接放在普通用户目录里我习惯在云上维护一份/etc/letsencrypt/live/example.com并且把整个证书目录纳入备份。这样即使云服务器重装也能快速恢复到原状不用再折腾证书迁移流程。我自己在实操中最深的体会是证书迁移本身不难难的是想清楚“谁签发、谁部署、谁续期”这三个角色。本地Ubuntu负责签发和续期云服务器负责部署和提供HTTPS服务中间的同步交给脚本自动完成。只要这个模型建立起来后续基本不会再为证书头疼。如果只是单次迁移注意带全完整证书链、保护好私钥、正确配置nginx也就足够了。
返回列表