跳到主内容
芝麻园地
EN
← 返回博客
#ssl#certbot#letsencrypt#https#nginx

如何给自己的服务器免费签发 SSL 证书:Certbot + Let's Encrypt 从签发到自动续期

自己有一台服务器、一个域名,就想要 HTTPS?Let's Encrypt 免费给 90 天证书,Certbot 全自动搞定签发与续期。本文按真实踩坑流程,讲清 HTTP-01 验证原理、certbot --nginx 与 certonly 两种姿势、nginx 三段式 HTTPS 结构,以及『备案拦截、云防火墙、续期后要 reload』三个最容易卡壳的地方,最后用 dry-run 自检整条续期链路。

编程快车 19 分钟

给自己的服务器上一个 HTTPS,现在已经不需要花一分钱:Let’s Encrypt 免费签发证书(90 天有效),Certbot 负责签发和自动续期,剩下要你做的,只是把 nginx 配对、把几个“卡点”提前避开。

这套流程我最近完整跑过两遍——一台老服务器 mm,一台新购服务器 oo(换了新域名,连证书一起新签)。这篇把“怎么签”和“签完怎么保证永远不掉”讲透,也把新手最容易卡住的三处单拎出来。

先搞清两件事:免费证书为什么“短命”,验证为什么走 80 端口

证书是免费的,但只有 90 天。 这不是抠门,是 Let’s Encrypt 的设计:逼着你把“续期”自动化,而不是签一次忘三年(付费证书有效一年,恰恰容易忘了换,等到过期才被发现)。90 天的有效期 + 每天自动检查、临期才续签,等于把“证书过期”这个事故从根上排除。所以后面“自动续期”不是可选项,是这套免费方案的一半。

验证你确实拥有这个域名,靠的是 HTTP-01 挑战。 大致过程:

  1. 你在服务器上运行 certbot,它向 Let’s Encrypt 申请:“我要给 example.com 签证书”;
  2. Let’s Encrypt 返回一个一次性令牌:请把内容 X 放到 http://example.com/.well-known/acme-challenge/X 上;
  3. certbot 自动在你服务器的 80 端口把这个路径配上;
  4. 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 的三步与一句要领

  1. 准备:域名解析到位、80/443 在云防火墙放行、备案(大陆服务器)完成;
  2. 签发:certbot certonly --nginx -d 域名 -d www.域名,nginx 写成三段式并引用 live/ 下的证书路径;
  3. 续期:certbot.timer(或 cron)自动跑 renew + deploy-hook reload nginx,然后用 renew --dry-run 自检。

一句要领收尾:免费的代价不是“每 90 天手工续一次”,而是“把续期写进自动化”——前者会忘,后者不会。把 dry-run 跑绿的那一刻,HTTPS 这件事就真的可以不管了。