配置 Let's Encrypt 自动续期 HTTPS

阅读约 8 分钟

在为域名配置 Let's Encrypt 免费 HTTPS 证书时,使用 certbot --nginx 往往能一键搞定。但在实际生产环境中,由于历史配置残留或多次修改,很容易遇到 Nginx 配置冲突导致验证失败的情况。

本文记录了从踩坑(HTTP-01 验证失败)到排错(解决 conflicting server name),最终实现证书申请与自动续期配置的完整过程。

1. 现状与痛点

  • 服务器环境:Linux + Nginx
  • 目标域名example.comwww.example.com
  • 原有配置:域名已解析,Nginx 中已有 HTTP 80 端口及使用旧证书的 443 端口配置。
  • 痛点:尝试使用 Certbot 申请证书时,报错 Challenge failed,且 Nginx 不断提示 conflicting server name

2. 核心排错:解决 Nginx 域名冲突

申请证书时报错的关键信息如下:


nginx: [warn] conflicting server name "[example.com](http://example.com/)" on 0.0.0.0:80, ignored

Detail: Invalid response from [http://example.com/.well-known/acme-challenge/](http://example.com/.well-known/acme-challenge/)...: "<!doctype html>..."

原因分析

  1. Nginx 中存在多个 server 块同时监听 80 端口并声明了相同的 server_name,导致 Nginx 忽略了部分配置。
  2. Certbot 尝试访问验证文件时,请求未走到正确的验证目录,而是被错误地路由到了网站首页,返回了 HTML。

排查步骤

使用 grep 查找所有包含该域名的配置:


sudo grep -r "[example.com](http://example.com/)" /etc/nginx/

结果发现,域名同时出现在了主配置文件和其他子配置文件中,且主配置文件内部还有重复定义。

解决方案

合并配置,确保一个域名在同一个端口只出现一次。 删除或禁用多余的配置文件中的重复部分,只在主配置文件中保留一个结构清晰的 80 端口 server 块。

3. 正确配置 Nginx 与申请证书

3.1 规范 Nginx 配置文件

修改 Nginx 配置文件,关键是为 Certbot 的验证目录留出后门,避免被跳转规则拦截:


# ===== HTTP 80 端口配置 =====

server {

listen 80;

server_name [example.com](http://example.com/) [www.example.com](http://www.example.com/);

# 关键:放行 Certbot 验证目录,不做跳转!

location /.well-known/acme-challenge/ {

root /usr/share/nginx/html; # 确保此目录存在,或替换为你实际的网站根目录

}

# 其他所有 HTTP 请求,跳转到 HTTPS(推荐使用 return 301)

location / {

return 301 [https://$host$request_uri](https://$host$request_uri/);

}

}

# ===== HTTPS 443 端口配置 =====

server {

listen 443 ssl;

server_name [example.com](http://example.com/) [www.example.com](http://www.example.com/);

# 暂时保留旧证书路径,等申请成功后再替换

ssl_certificate /path/to/your/old_cert.crt;

ssl_certificate_key /path/to/your/old_cert.key;

# ... 其他网站业务配置 ...

}

💡 踩坑提示:HTTP 跳转 HTTPS 强烈建议使用 return 301 https://$host$request_uri; 而非 rewrite ^(.*) https://$server_name$1 permanent;。前者性能更好,且能完美保留原始域名(www 与裸域)及查询参数。

测试并重载 Nginx,确保不再出现警告:


sudo nginx -t && sudo systemctl reload nginx

3.2 执行 Certbot 申请证书

运行以下命令申请纯证书(使用 certonly 模式,不让 Certbot 乱动我们的 Nginx 配置):


sudo certbot certonly --nginx -d [example.com](http://example.com/) -d [www.example.com](http://www.example.com/)

此时验证将顺利通过,输出成功信息及证书路径:


Successfully received certificate.

Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem

Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem

3.3 替换 Nginx 证书路径

再次编辑 Nginx 配置,将 443 端口块的证书路径替换为 Let's Encrypt 签发的新路径:


server {

listen 443 ssl;

server_name [example.com](http://example.com/) [www.example.com](http://www.example.com/);

# 替换为 Let's Encrypt 证书路径

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;

ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;



# ...

}

重载 Nginx 生效:


sudo nginx -t && sudo systemctl reload nginx

4. 关键一步:配置自动续期与重载

Let's Encrypt 证书有效期仅 90 天。虽然 Certbot 自带了定时任务来检查续期,但使用 certonly 模式时,续期后 Nginx 不会自动重载,会导致旧证书仍在内存中生效,最终过期。

我们需要添加一个 deploy_hook,让续期成功后自动重载 Nginx。

编辑续期配置文件:


sudo nano /etc/letsencrypt/renewal/[example.com](http://example.com/).conf

在文件末尾添加一行:


deploy_hook = systemctl reload nginx

(保存退出:Ctrl+O -> Enter -> Ctrl+X)

模拟自动续期测试


sudo certbot renew --dry-run

若输出以下关键信息,则大功告成:


new certificate deployed with reload of nginx server; fullchain is...

Congratulations, all renewals succeeded.

5. 总结

  • 遇到 conflicting server name 必须清理 Nginx 重复配置,这是 HTTP-01 验证失败的万恶之源。
  • 80 端口配置必须优先放行 .well-known/acme-challenge/ 目录。
  • 推荐使用 return 301 进行跳转。
  • 使用 certonly 模式时,务必手动配置 deploy_hook 以实现证书续期后自动重载 Nginx,否则自动化是不完整的。

按照以上流程,即可一劳永逸地解决 Let's Encrypt 证书的申请与自动续期问题。