在Debian 12和Ubuntu 24.04上为Nginx配置Let's Encrypt SSL/TLS证书
使用Certbot为Nginx获取并自动续期免费TLS证书。涵盖DNS配置、Certbot安装、HTTP到HTTPS重定向、TLS安全加固、HTTP/2、HSTS以及OCSP停用说明。适用于Debian 12和Ubuntu 24.04。
在Debian 12和Ubuntu 24.04上为Nginx配置Let's Encrypt SSL/TLS证书
本教程介绍如何使用Certbot从Let's Encrypt获取免费TLS证书、为Nginx配置HTTPS,以及设置自动续期。每个步骤都包含验证命令,方便你在继续之前确认操作结果。
如果你还没有安装Nginx,请先阅读在Debian 12和Ubuntu 24.04上安装Nginx。关于在VPS上管理Nginx的全面介绍,请参阅VPS上的Nginx管理。
申请证书前需要准备什么?
在Certbot颁发证书之前,你的域名必须指向服务器的IP地址,且Nginx必须正在运行并配置了该域名的server block(服务器块)。Let's Encrypt通过向你的服务器发送HTTP请求来验证域名所有权。如果DNS没有解析到你的VPS,或者Nginx未在监听,验证将失败。
你需要:
- 一台运行Debian 12或Ubuntu 24.04的VPS,已从官方仓库安装Nginx(在Debian 12和Ubuntu 24.04上安装Nginx)
- 一个已注册的域名(本文以
example.com为例) - 一条A记录,将
example.com指向你服务器的IPv4地址 - 一条AAAA记录,指向你的IPv6地址(如果服务器有IPv6)
- 防火墙开放80端口(Certbot使用HTTP-01验证)
- 一个可用的Nginx server block配置(Nginx Server Blocks)
设置DNS记录
在你的DNS提供商处创建A记录:
| 类型 | 名称 | 值 | TTL |
|---|---|---|---|
| A | example.com | 203.0.113.10 | 300 |
| AAAA | example.com | 2001:db8::1 | 300 |
将IP地址替换为你服务器的实际地址。设置期间TTL使用较低值(300秒),这样变更传播更快。后续可以调高。
验证DNS解析
创建记录后等待几分钟,然后从你的本地机器(不是服务器)验证:
dig +short example.com A
dig +short example.com AAAA
你应该在输出中看到你服务器的IP地址。如果什么都没有显示或显示了不同的IP,说明记录尚未传播,等待后再试。
从本地机器验证Nginx在80端口有响应:
curl -I http://example.com
你应该收到HTTP/1.1 200 OK响应,且包含Server: nginx。如果连接超时,请检查防火墙规则。
如何在Debian 12和Ubuntu 24.04上安装Certbot?
使用apt从发行版软件包仓库安装Certbot及其Nginx插件。Nginx插件允许Certbot自动修改你的server block以启用TLS。
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
验证安装:
certbot --version
在Debian 12上会安装Certbot 2.1.0,Ubuntu 24.04上为Certbot 2.9.0。两个版本都适用于本教程的所有内容。
注意:如果你按照在Debian 12和Ubuntu 24.04上安装Nginx的推荐从官方nginx.org仓库安装了Nginx,Certbot的Nginx插件无需额外配置即可工作。它会检测/etc/nginx/conf.d/和/etc/nginx/sites-enabled/中的server block。
如何为Nginx获取Let's Encrypt证书?
使用你的域名运行certbot --nginx。Certbot会联系Let's Encrypt,通过HTTP-01验证证明你拥有该域名,获取证书,并编辑你的Nginx server block以使用该证书。整个过程大约需要30秒。
sudo certbot --nginx -d example.com -d www.example.com
Certbot会询问你的电子邮件地址(用于续期提醒)以及是否同意服务条款。然后它会:
- 在你的web根目录放置一个HTTP-01验证文件
- 请求Let's Encrypt进行验证
- 下载已签名的证书
- 修改你的Nginx server block,添加TLS指令
- 重新加载Nginx
验证证书是否已颁发:
sudo ls -la /etc/letsencrypt/live/example.com/
你应该看到:
lrwxrwxrwx 1 root root ... cert.pem -> ../../archive/example.com/cert1.pem
lrwxrwxrwx 1 root root ... chain.pem -> ../../archive/example.com/chain1.pem
lrwxrwxrwx 1 root root ... fullchain.pem -> ../../archive/example.com/fullchain1.pem
lrwxrwxrwx 1 root root ... privkey.pem -> ../../archive/example.com/privkey1.pem
这些是符号链接。fullchain.pem是你的证书加中间CA证书链。privkey.pem是你的私钥。
检查Nginx是否使用新配置正常运行:
sudo nginx -t && sudo systemctl status nginx
nginx -t会测试配置语法。如果输出test is successful,说明配置有效。
Certbot对你的Nginx配置做了哪些更改?
Certbot会在你的server block中添加多行内容。以下是它插入的内容(标记为# managed by Certbot的行):
server {
server_name example.com www.example.com;
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# ... your existing location blocks ...
}
options-ssl-nginx.conf文件包含Certbot的默认TLS设置。我们将在下面的加固部分用更强的设置替换它们。
Certbot还会创建第二个server block来将HTTP重定向到HTTPS。我们将在下一节改进这个重定向。
你可以通过比较配置文件查看具体更改了什么:
sudo diff /etc/nginx/conf.d/example.com.conf /etc/nginx/conf.d/example.com.conf.bak 2>/dev/null || echo "No backup found. Certbot modifies in place."
如何在Nginx中将HTTP重定向到HTTPS?
所有HTTP流量都应使用301(永久)重定向到HTTPS。Certbot可能会自动添加此重定向,但其默认方式是在现有server block内使用if语句。这在Nginx中是一种反模式。使用独立的server block更简洁、更可靠。
用独立的server block替换Certbot的重定向。编辑你的配置文件(路径取决于你的设置,通常为/etc/nginx/conf.d/example.com.conf):
# HTTP -> HTTPS redirect (separate server block, not an if-statement)
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
这段配置可以放在HTTPS server block的同一个文件中,也可以放在单独的文件中。确保删除Certbot生成的重定向块以避免重复。
测试并重新加载:
sudo nginx -t
sudo systemctl reload nginx
从本地机器验证重定向:
curl -I http://example.com
预期输出:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
如何加固生产服务器的TLS设置?
Certbot的默认TLS配置(options-ssl-nginx.conf)有意设置得比较保守。对于生产服务器,你需要更严格的设置。我们将遵循Mozilla的Intermediate配置文件,来源于SSL Configuration Generator。该配置在安全性和客户端兼容性之间取得平衡,向下兼容至Firefox 27、Chrome 31和Android 4.4.2。
创建一个snippet(代码片段)文件,可在每个server block中通过include引用:
sudo nano /etc/nginx/snippets/tls-params.conf
添加以下内容:
# TLS protocol versions — TLS 1.2 and 1.3 only
# TLS 1.0 and 1.1 are deprecated (RFC 8996)
ssl_protocols TLSv1.2 TLSv1.3;
# Ciphers — Mozilla Intermediate profile (January 2026)
# Source: https://ssl-config.mozilla.org/
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# DH parameters — 2048-bit, RFC 7919 ffdhe2048
ssl_dhparam /etc/nginx/dhparam.pem;
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:TLS:10m;
ssl_session_tickets off;
# HSTS — tell browsers to always use HTTPS (2 years)
# Only add includeSubDomains if ALL subdomains use HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# Hide Nginx version in error pages and headers
server_tokens off;
生成DH参数文件(需要几秒钟):
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
验证文件已创建:
sudo ls -la /etc/nginx/dhparam.pem
现在更新你的HTTPS server block,使用这些设置替代Certbot的默认配置。删除include /etc/letsencrypt/options-ssl-nginx.conf;和ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;这两行,替换为:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS hardening (replaces Certbot defaults)
include snippets/tls-params.conf;
# ... your location blocks ...
}
测试并重新加载:
sudo nginx -t
sudo systemctl reload nginx
应该使用哪些TLS版本和密码套件?
Mozilla发布了三种TLS配置方案,以下是对比:
| 配置方案 | 协议 | 最低兼容客户端 | 适用场景 |
|---|---|---|---|
| Modern | 仅TLS 1.3 | Firefox 63、Chrome 70、Android 10 | 所有客户端都是新版本的服务 |
| Intermediate | TLS 1.2 + 1.3 | Firefox 27、Chrome 31、Android 4.4 | 通用Web服务器 |
| Old | TLS 1.0 + 1.1 + 1.2 + 1.3 | Firefox 1、Chrome 1、IE 8 | 仅用于遗留系统 |
推荐使用Intermediate,除非你有特殊原因。它覆盖99.9%以上的当前浏览器,同时排除了弱协议。TLS 1.0和1.1已于2021年3月被RFC 8996正式弃用。
我们snippet中的密码列表仅使用AEAD密码(GCM和ChaCha20-Poly1305)。ssl_prefer_server_ciphers off让客户端选择其首选密码。这是Mozilla的推荐做法,因为现代客户端比静态的服务器端偏好设置能做出更好的选择。
Let's Encrypt还支持OCSP stapling吗?
不支持。Let's Encrypt于2025年8月6日关闭了其OCSP服务。2025年5月之后颁发的证书不包含OCSP响应器URL。吊销状态现在仅通过CRL(Certificate Revocation Lists,证书吊销列表)发布。如果你只使用Let's Encrypt证书,请从Nginx配置中删除所有ssl_stapling指令。
以下是时间线:
- **2024年12月。**Let's Encrypt宣布计划终止OCSP。
- **2025年1月30日。**请求OCSP Must-Staple扩展的证书开始失败。
- **2025年5月7日。**新证书停止包含OCSP响应器URL,改为添加CRL URL。
- **2025年8月6日。**OCSP服务完全关闭。所有之前颁发的包含OCSP URL的证书都已过期。
如果你在任何指南或配置片段中看到ssl_stapling on;或ssl_stapling_verify on;,这对Let's Encrypt用户来说是过时的建议。这些指令是无害的(当没有OCSP响应器URL时,Nginx会静默忽略它们),但会增加不必要的冗余。建议删除。
如果你使用的是其他仍提供OCSP的CA颁发的证书,这些指令对那些证书仍然有效。
**为什么Let's Encrypt放弃了OCSP?**两个原因。OCSP是一个隐私风险:每当浏览器通过OCSP检查证书吊销状态时,CA就会知道哪个IP访问了哪个网站。在高峰期,Let's Encrypt的OCSP服务每月处理3400亿次请求。切换到CRL消除了这种隐私泄露。CRL由浏览器批量下载,因此不会有针对每次访问的请求发送到CA。
如何在Nginx中启用HTTP/2?
HTTP/2通过单个连接多路复用请求,降低延迟。如果你从官方nginx.org仓库安装了Nginx(1.25.1或更高版本),可以在server block中使用http2指令启用HTTP/2。
在HTTPS server block中添加http2 on;:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include snippets/tls-params.conf;
# ... your location blocks ...
}
http2 on;指令替代了Nginx 1.25.1中已弃用的listen 443 ssl http2;语法。旧语法仍然有效,但会在错误日志中产生弃用警告。如果你运行的是发行版自带的Nginx(Debian 12为1.22,Ubuntu 24.04为1.24),请使用旧的listen 443 ssl http2;语法。
测试并重新加载:
sudo nginx -t
sudo systemctl reload nginx
验证HTTP/2是否已启用:
curl -I --http2 -s https://example.com | head -1
预期输出:
HTTP/2 200
如果你看到HTTP/1.1,请检查http2 on;是否在正确的server block内,以及你的Nginx版本是否支持此指令。
证书自动续期如何工作?
Let's Encrypt证书在90天后过期。Certbot安装了一个systemd timer(certbot.timer),每天检查两次是否有证书将在30天内到期。如果有,会自动续期。你不需要设置cron job。
检查timer是否处于活动状态:
systemctl status certbot.timer
你应该看到Active: active (waiting)以及显示下次触发时间的行。
查看下次续期的时间:
systemctl list-timers certbot.timer
这会显示NEXT和LAST运行时间。
不实际续期的情况下测试续期流程
运行dry-run来验证续期流程是否端到端正常工作:
sudo certbot renew --dry-run
这会联系Let's Encrypt的staging(测试)服务器并模拟完整的续期过程。如果输出Congratulations, all simulated renewals succeeded,说明你的设置是正确的。
设置deploy hook在续期后重新加载Nginx
当Certbot续期证书后,Nginx需要重新加载以使用新文件。配置一个仅在续期成功后运行的deploy hook:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
添加:
#!/bin/bash
/usr/bin/systemctl reload nginx
设置可执行权限:
sudo chmod 700 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
验证权限:
ls -la /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
预期输出显示-rwx------(只有root可以读取和执行)。
deploy hook目录中的脚本仅在续期成功后运行,而不是每次timer触发时都运行。这避免了不必要的重新加载。
如何验证TLS设置是否正确?
完成上述所有步骤后,运行以下验证命令。每个命令检查设置的不同方面。
使用OpenSSL检查证书链
从本地机器执行:
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer
预期输出:
notBefore=Mar 19 00:00:00 2026 GMT
notAfter=Jun 17 00:00:00 2026 GMT
subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R12
注意:notAfter日期应大约在颁发日期后90天。颁发者应为Let's Encrypt。当前的RSA中间证书为R12和R13。对于ECDSA证书,应为E7或E8。
检查正在使用的TLS版本和密码套件
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | grep -E "Protocol|Cipher"
预期输出:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
如果你看到TLSv1.2,也是正常的。这取决于本地OpenSSL优先选择的版本。
检查HTTPS响应头
curl -I https://example.com
查找:
HTTP/2 200
strict-transport-security: max-age=63072000; includeSubDomains
如果缺少strict-transport-security,请检查TLS snippet中的add_header行。always参数确保即使在错误响应中也会发送该头部。
验证命令汇总
| 命令 | 检查内容 | 期望结果 |
|---|---|---|
dig +short example.com |
DNS解析 | 你服务器的IP |
curl -I http://example.com |
HTTP重定向 | 301加Location: https:// |
curl -I https://example.com |
HTTPS + 响应头 | HTTP/2 200、HSTS头部 |
openssl s_client -connect ... |
证书链、TLS版本 | Let's Encrypt颁发者、TLSv1.2或1.3 |
certbot renew --dry-run |
续期流程 | all simulated renewals succeeded |
systemctl status certbot.timer |
自动续期timer | active (waiting) |
使用SSL Labs测试
如需全面的外部审计,请将你的域名提交到SSL Labs Server Test。使用本指南的配置,你应该获得A或A+评分。A+需要HSTS,我们已在TLS snippet中启用了它。
完整的Nginx配置参考
以下是本教程所有设置合并后的完整server block:
# HTTP -> HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# HTTPS server block
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
# Let's Encrypt certificate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS hardening
include snippets/tls-params.conf;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
以及位于/etc/nginx/snippets/tls-params.conf的TLS snippet:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:TLS:10m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
server_tokens off;
关于Content-Security-Policy、X-Frame-Options和Permissions-Policy等其他安全头部,请参阅Nginx安全加固。
遇到问题了?
Certbot提示"Could not automatically find a matching server block"
Certbot会查找server_name指令与你-d域名匹配的配置。确保你的server block文件在/etc/nginx/conf.d/或/etc/nginx/sites-enabled/中,并且包含server_name example.com;。
Certbot提示"Connection refused"或"Challenge failed" 80端口必须开放。检查防火墙:
sudo ufw status # if using UFW
sudo nft list ruleset # if using nftables
同时验证Nginx正在监听80端口:
sudo ss -tlnp | grep ':80'
Nginx错误日志中出现"SSL: error" 你可能在密码字符串中有语法错误,或文件路径缺失。检查:
sudo nginx -t
sudo journalctl -u nginx -n 20 --no-pager
浏览器显示"NET::ERR_CERT_DATE_INVALID" 证书可能已过期。检查到期时间:
sudo certbot certificates
如果已过期,强制续期:
sudo certbot renew --force-renewal
certbot renew --dry-run失败
常见原因:域名的DNS不再指向此服务器,或80端口被阻止。Certbot需要HTTP-01访问来完成续期。
HTTP/2不工作
检查你的Nginx版本:nginx -v。如果低于1.25.1,使用listen 443 ssl http2;而不是单独的http2 on;指令。如果是1.25.1或更高版本,确保http2 on;在正确的server块内(不是http或location块内)。
Copyright 2026 Virtua.Cloud. All rights reserved.