给自己的服务器上一个 HTTPS,现在已经不需要花一分钱:Let’s Encrypt 免费签发证书(90 天有效),Certbot 负责签发和自动续期,剩下要你做的,只是把 nginx 配对、把几个“卡点”提前避开。
这套流程我最近完整跑过两遍——一台老服务器 mm,一台新购服务器 oo(换了新域名,连证书一起新签)。这篇把“怎么签”和“签完怎么保证永远不掉”讲透,也把新手最容易卡住的三处单拎出来。
先搞清两件事:免费证书为什么“短命”,验证为什么走 80 端口
证书是免费的,但只有 90 天。 这不是抠门,是 Let’s Encrypt 的设计:逼着你把“续期”自动化,而不是签一次忘三年(付费证书有效一年,恰恰容易忘了换,等到过期才被发现)。90 天的有效期 + 每天自动检查、临期才续签,等于把“证书过期”这个事故从根上排除。所以后面“自动续期”不是可选项,是这套免费方案的一半。
验证你确实拥有这个域名,靠的是 HTTP-01 挑战。 大致过程:
- 你在服务器上运行 certbot,它向 Let’s Encrypt 申请:“我要给
example.com签证书”; - Let’s Encrypt 返回一个一次性令牌:请把内容
X放到http://example.com/.well-known/acme-challenge/X上; - certbot 自动在你服务器的 80 端口把这个路径配上;
- Let’s Encrypt 从公网访问这个地址,拿到
X,确认域名归你 → 发证书。
注意两个推论,后面全篇都围绕它们:
- 验证走的是 HTTP + 80 端口,所以域名必须从公网能访问到这台机器、80 端口必须通——证书验证不需要 443,443 是你拿到证书后才开的。
- 验证“你的域名确实指向这台服务器”。域名解析没生效、或 80 被墙/被拦,都会失败。
准备:域名解析 + 两个“看不见的拦路虎”
动手前确认三件事:
nslookup yourdomain.com # 域名已解析到这台服务器
ss -tlnp | grep ':80' # 80 端口有东西在听(nginx)
nginx -v && certbot --version # certbot 装了没(见下)
装 certbot(Ubuntu/Debian,含 nginx 插件):
sudo apt update
sudo apt install certbot python3-certbot-nginx
装完 certbot --version 有输出版本号即可。下面这两个“看不见的拦路虎”,是我真正踩过、报错还很迷惑的地方:
拦路虎一:未备案域名,80 会被云厂商在“网络层”拦截。 大陆服务器上,域名没完成 ICP 备案前,云厂商会在边缘直接把对它的 HTTP 访问 302 到“请备案”的拦截页。你在服务器里 curl -H "Host: yourdomain.com" http://127.0.0.1/ 明明 200,但 certbot 从公网去验证时拿到的是拦截页,报错长这样:
certbot --nginx 报错:Domain yourdomain.com
Type: unauthorized, Invalid response from https://dnspod.qcloud.com/.../webblock.html
这不是配置问题,是备案没完成。先去把域名备案走完,备案生效前 HTTP-01 这道验证注定过不去(备选是 DNS-01,见文末)。
拦路虎二:云防火墙和主机防火墙是两层。 “云安全组/防火墙”在云控制台里(SSH 改不到),“主机防火墙”(ufw/iptables)才在服务器里。80 被云防火墙挡住,验证一样失败;443 被挡住,则证书签好也连不上。排障顺序记牢:回环自测通过 → 监听在 → 主机防火墙没拦 → 那就去云控制台放行端口。
签发:两种姿势,按你的洁癖程度选
证书最终落在 /etc/letsencrypt/live/<你的域名>/,里面是 fullchain.pem(证书链)和 privkey.pem(私钥),nginx 引用的就是这两个软链接——续期后链接不动、指向新文件,所以 nginx 配置里写死这个路径即可。
姿势一:certbot --nginx —— 全自动,连 nginx 都帮你改
如果你的 nginx 站点结构简单、不介意 certbot 改写配置文件,一行搞定:
sudo certbot --nginx -d example.com -d www.example.com \
--non-interactive --agree-tos --register-unsafely-without-email --redirect
它自动完成:临时配置验证路径 → 验证 → 签发 → 改写 nginx 加 443 和证书 → 加上 80 跳 443(--redirect)→ 建好续期定时任务。适合想 5 分钟上线的人。
代价是配置文件会被 certbot 接管一部分(会有 # managed by Certbot 标记),以后你手动改结构时容易和它互相踩。改配置前先备份一份是铁律。
姿势二:certbot certonly --nginx —— 只签证书,nginx 自己写
如果你对站点结构有讲究(比如下面这套 www 归一、未知 Host 一律 404 的三段式),用 certonly 只让 certbot 临时借 nginx 完成验证、把证书签下来,然后立刻收手,配置完全自己掌控:
sudo certbot certonly --nginx -d example.com -d www.example.com
# 证书落在 /etc/letsencrypt/live/example.com/
签完自己把 nginx 站点写成下面这种结构。
一套干净的 nginx HTTPS 结构(三段式)
这是我在两台机器上都在用的结构,三段职责分明:
# 段一:www 归一 —— www.xxx → 直接 301 到 apex 主域
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # certbot 装的推荐 SSL 参数
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
return 301 https://example.com$request_uri;
}
# 段二:apex 主域 443 —— 真正的站点
server {
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;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/site;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# ……反代、静态缓存等按需加
}
# 段三:80 → 443,且只认自己的域名
server {
listen 80;
server_name example.com www.example.com;
if ($host = www.example.com) { return 301 https://example.com$request_uri; }
if ($host = example.com) { return 301 https://$host$request_uri; }
return 404; # 未知 Host 一律 404,别把别人的请求接进你的站
}
include options-ssl-nginx.conf 和 ssl_dhparam 这两个文件是 certbot 安装时生成的推荐配置,帮你省掉一堆 SSL 参数选择。写完后:
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://example.com/ # → 200,且证书有效
一个细节:try_files $uri $uri/ =404(而不是回退首页)能让不存在的路径返回真正的 404,配合 error_page 404 /404.html;,对 SEO 和排障都更友好。
免费方案的“另一半”:自动续期
签好了只是开始。Let’s Encrypt 证书 90 天就过期,续期必须自动化。
续期核心命令就一个:sudo certbot renew。它会扫描 /etc/letsencrypt/renewal/ 下的配置,只在距到期不足 30 天时才真正去续,否则秒退,说 “Certificate not yet due for renewal”。所以你可以放心地让它高频跑,平时开销几乎为零。
谁来定时跑它?Ubuntu 上装 certbot 后自带一个 systemd 定时器,叫 certbot.timer:
$ systemctl cat certbot.timer
Description=Run certbot twice daily
OnCalendar=*-*-* 00,12:00:00 # 每天 UTC 0 点和 12 点各触发一次
RandomizedDelaySec=43200 # 再随机延迟 0~12 小时,避免全网服务器同时挤兑
确认它活着:systemctl is-enabled certbot.timer 和 is-active 都该是 active/enabled;systemctl list-timers certbot.timer 能看到下次触发时间。老一些的机器(或你手动配)用 cron 也行,一行即可,效果等同:
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
为什么有个 --deploy-hook "systemctl reload nginx"? 这是最容易漏的一环:证书续期后,live/ 下的软链接会指向新文件,但正在跑的 nginx 还记着旧文件,不 reload 它不会换新证书。deploy hook 在“真的续了”之后才执行,把 nginx reload 一下。定时任务里带不带这个 hook,决定了到期后证书到底有没有真正生效。
自检整条链路:dry-run
配完别干等 90 天,立刻模拟续一次验证整条链路能不能通:
sudo certbot renew --dry-run
它走 Let’s Encrypt 的测试环境、不会真签发也不占正式限额,把验证、续期、deploy-hook 全流程演一遍。看到这句就说明链路是通的:
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
日常查看证书状态与到期时间:
sudo certbot certificates
# Certificate Name: example.com
# Domains: example.com www.example.com
# Expiry Date: 2026-12-03 (VALID: 89 days)
踩坑速查
| 症状 | 原因 | 处理 |
|---|---|---|
Type: unauthorized ... webblock |
域名未完成 ICP 备案,HTTP 被云厂商拦截 | 先去备案;或用 DNS-01 验证 |
| certbot 验证超时 / 连不上 80 | 域名没解析到这台机,或云防火墙没放行 80 | nslookup 核对;云控制台放行 80 |
| 证书签好但 https 打不开 | 云防火墙没放行 443 | 云控制台放行 443;ss -tlnp 确认在监听 |
| 续期显示成功但证书没变 | 没配 deploy-hook reload nginx | 加 --deploy-hook "systemctl reload nginx" |
想同时管 *.example.com |
HTTP-01 只能验证单域名 | 换 DNS-01(如 certbot-dns-cloudflare)走 API 验证 |
小结:免费 SSL 的三步与一句要领
- 准备:域名解析到位、80/443 在云防火墙放行、备案(大陆服务器)完成;
- 签发:
certbot certonly --nginx -d 域名 -d www.域名,nginx 写成三段式并引用live/下的证书路径; - 续期:
certbot.timer(或 cron)自动跑renew+ deploy-hook reload nginx,然后用renew --dry-run自检。
一句要领收尾:免费的代价不是“每 90 天手工续一次”,而是“把续期写进自动化”——前者会忘,后者不会。把 dry-run 跑绿的那一刻,HTTPS 这件事就真的可以不管了。