引言:HTTPS 已不再是可选项,而是必需品

在 2026 年的今天,HTTPS 已成为网站的必备要素。Google Chrome、Firefox、Safari 等所有主流浏览器都会在 HTTP 网站上显示"不安全"警告,Google 也会为 HTTPS 网站提供搜索排名加分。此外,HTTP/2、HTTP/3、Service Worker 等最新 Web 技术仅在 HTTPS 环境下才能运行。

过去申请一份 SSL 证书每年要花费数十万韩元,但自从 Let's Encrypt 出现后,情况彻底改变了。成立于 2015 年的 Let's Encrypt 是一家非营利证书颁发机构(CA),让任何人都可以完全免费申请到 SSL 证书。截至 2026 年,它已成为全球使用最广泛的免费 SSL 证书,每天签发数百万份证书。

本指南将涵盖使用 Let's Encrypt 将 HTTPS 应用于实际服务的全部流程。从 Certbot 安装到 Nginx/Apache 应用、通配符证书、自动续期,以及常见问题与解决方法,全部详细讲解,助你在实际工作中立即上手。

1. Let's Encrypt 与 SSL 证书基础

1.1 Let's Encrypt 的特点

  • 完全免费:申请、续期、重新签发全部免费
  • 自动化:通过 ACME 协议可实现签发/续期自动化
  • 有效期 90 天:比商业证书(1~2 年)短,但可通过自动续期解决
  • 仅支持域名验证(DV):不支持组织验证(OV)和扩展验证(EV)
  • 支持通配符:可以签发 *.example.com 形式的证书
  • 所有主流浏览器均信任:Chrome、Firefox、Safari、Edge 均自动信任

1.2 理解验证方式

Let's Encrypt 通过验证域名所有权的质询(Challenge)机制来签发证书:

质询类型 验证方式 用途
HTTP-01 在 Web 服务器特定路径放置文件进行验证 最常见,单域名
DNS-01 通过 DNS TXT 记录验证 通配符证书必需,可自动化
TLS-ALPN-01 在 TLS 握手期间验证 特殊环境(如 80 端口被封)
应该选择哪种方式?
• 单域名(example.com、www.example.com):HTTP-01(最简单)
• 通配符(*.example.com):必须使用 DNS-01
• 内网服务器、80 端口被封:使用 DNS-01

1.3 申请前的准备事项

  • 域名所有权:需要拥有可实际管理的域名
  • DNS 配置:域名需正确解析到服务器 IP
  • 开放 80/443 端口:HTTP-01 方式要求 80 端口可从外部访问
  • root 权限:用于证书安装和 Web 服务器配置修改

2. 安装 Certbot

Certbot 是由 EFF(电子前沿基金会)开发的 Let's Encrypt 官方客户端,可自动化完成证书的申请与管理。

2.1 Ubuntu/Debian 安装

# snap 方式(官方推荐)
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

# 或 apt 方式(简便)
sudo apt update
sudo apt install certbot -y
sudo apt install python3-certbot-nginx -y    # Nginx 插件
sudo apt install python3-certbot-apache -y   # Apache 插件(如需要)

# 查看版本
certbot --version

2.2 RHEL/Rocky Linux/CentOS 安装

# 启用 EPEL 仓库(RHEL/CentOS 7)
sudo yum install epel-release -y
sudo yum install certbot python3-certbot-nginx -y

# RHEL 9 / Rocky Linux 9
sudo dnf install epel-release -y
sudo dnf install certbot python3-certbot-nginx -y

# snap 方式(所有版本通用)
sudo dnf install snapd -y
sudo systemctl enable --now snapd.socket
sudo ln -s /var/lib/snapd/snap /snap
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

3. 在 Nginx 上应用 SSL 证书

3.1 自动配置(最简单的方法)

使用 Certbot 的 Nginx 插件,可以一次性完成证书签发和 Nginx 配置修改:

# 前提条件:Nginx 中需已存在该域名对应的 server 块
# 例如:server_name example.com www.example.com;

# 签发证书并自动配置 Nginx
sudo certbot --nginx -d example.com -d www.example.com

# 运行时将询问以下信息:
# 1. 输入邮箱地址(用于接收过期提醒)
# 2. 同意使用条款(Y)
# 3. 是否接收 EFF 通讯(可选 N)
# 4. HTTP → HTTPS 重定向配置(建议选 2)

成功时将输出类似下面的信息:

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
This certificate expires on 2026-07-05.

Deploying certificate
Successfully deployed certificate for example.com
Successfully deployed certificate for www.example.com
Your existing server block has been modified to redirect HTTP traffic to HTTPS.

3.2 手动配置(仅签发证书)

如果想自己编写 Nginx 配置,可以只签发证书而手动编写配置:

# webroot 方式(Nginx 运行中)
sudo certbot certonly --webroot -w /var/www/example.com/html \
  -d example.com -d www.example.com

# standalone 方式(临时停止 Nginx 后签发)
sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com -d www.example.com
sudo systemctl start nginx
# 手动 Nginx 配置示例
# /etc/nginx/conf.d/example.com.conf

# HTTP → HTTPS 重定向
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS 主配置
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com 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;

    # SSL 最佳配置
    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;
    ssl_prefer_server_ciphers off;

    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;

    # HSTS(强制 HTTPS)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    root /var/www/example.com/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
# 验证配置并应用
sudo nginx -t
sudo systemctl reload nginx

4. 在 Apache 上应用 SSL 证书

4.1 自动配置

# 启用 Apache SSL 模块(Ubuntu/Debian)
sudo a2enmod ssl
sudo a2enmod headers
sudo systemctl reload apache2

# 使用 Certbot Apache 插件自动配置
sudo certbot --apache -d example.com -d www.example.com

4.2 手动 Apache 配置

# /etc/apache2/sites-available/example.com-ssl.conf
<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/html

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    # SSL 协议(TLS 1.2 及以上)
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder off
    SSLSessionTickets off

    # OCSP Stapling
    SSLUseStapling on
    SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

    # HSTS
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    ErrorLog ${APACHE_LOG_DIR}/example.com-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-ssl-access.log combined
</VirtualHost>
</IfModule>

# HTTP → HTTPS 重定向
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>
# 启用站点
sudo a2ensite example.com-ssl.conf

# 验证配置并应用
sudo apachectl configtest
sudo systemctl reload apache2

5. 签发通配符证书(DNS-01)

通配符证书(*.example.com)只需一张证书即可覆盖所有子域名(app.example.com、api.example.com 等),非常方便。但它只支持 DNS-01 质询。

5.1 手动 DNS-01 签发

# 手动 DNS 质询
sudo certbot certonly --manual --preferred-challenges=dns \
  -d example.com -d "*.example.com"

# Certbot 会输出类似下面的信息:
# Please deploy a DNS TXT record under the name:
# _acme-challenge.example.com
# with the following value:
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 将上述 TXT 记录手动添加到 DNS 管理面板

# 添加后确认 DNS 传播情况(按 Enter 前)
dig -t txt _acme-challenge.example.com

# 确认 DNS 已传播后按 Enter 进行验证
注意:DNS TXT 记录的传播可能需要一些时间(最长 24 小时,通常为数分钟到数十分钟)。请先使用 dig 命令确认传播完成后再按 Enter。过早按 Enter 会导致验证失败。

5.2 DNS API 自动化(Cloudflare 示例)

如果你使用 Cloudflare、Route53、Google Cloud DNS 等,可通过 API 实现自动化:

# 安装 Cloudflare 插件
sudo snap install certbot-dns-cloudflare
# 或
sudo apt install python3-certbot-dns-cloudflare -y

# 保存 Cloudflare API Token (/root/.secrets/cloudflare.ini)
sudo mkdir -p /root/.secrets
sudo cat > /root/.secrets/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
sudo chmod 600 /root/.secrets/cloudflare.ini

# 自动签发通配符证书
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d "*.example.com"
# Route53 插件 (AWS)
sudo snap install certbot-dns-route53

sudo certbot certonly \
  --dns-route53 \
  -d example.com -d "*.example.com"
# (IAM 凭证需配置在环境变量或 ~/.aws/credentials 中)

6. 证书自动续期

Let's Encrypt 证书每 90 天需要续期一次。Certbot 默认支持自动续期。

6.1 检查自动续期

# 自动续期测试(不实际续期,仅模拟)
sudo certbot renew --dry-run

# 强制续期(即使距到期还有 30 天以上)
sudo certbot renew --force-renewal

# 仅续期指定域名
sudo certbot renew --cert-name example.com

# 自动续期定时器状态 (systemd)
sudo systemctl status certbot.timer

# cron 方式(snap 版本自动配置)
systemctl list-timers | grep certbot

6.2 自动续期机制

Certbot 在安装时会配置自动续期机制:

  • systemd timer/etc/systemd/system/certbot.timer(snap/最新安装)
  • cron/etc/cron.d/certbot(apt 安装)
  • 运行周期:每天 2 次(每 12 小时)
  • 续期条件:仅在到期 30 天以内的证书才会实际续期
# 手动注册 cron(如需要)
sudo crontab -e

# 每天凌晨 3 点检查自动续期
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

# 续期后执行指定命令 (hook)
sudo certbot renew --post-hook "systemctl reload nginx"
sudo certbot renew --deploy-hook "systemctl reload nginx"

6.3 配置续期失败通知

# 修改通知邮箱
sudo certbot update_account --email new@example.com

# 续期失败时执行脚本
# /etc/letsencrypt/renewal-hooks/deploy/notify.sh
#!/bin/bash
echo "Certificate renewed: $RENEWED_DOMAINS" | mail -s "SSL Renewal" admin@example.com

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/notify.sh

7. 证书管理命令

7.1 常用 Certbot 命令

# 查看已签发证书列表
sudo certbot certificates

# 删除指定证书
sudo certbot delete --cert-name example.com

# 重新签发证书(添加其他域名)
sudo certbot certonly --expand -d example.com -d www.example.com -d blog.example.com

# 使用 ECDSA 密钥(比 RSA 更快、更小)
sudo certbot certonly --nginx --key-type ecdsa --elliptic-curve secp384r1 \
  -d example.com -d www.example.com

# 使用指定邮箱注册
sudo certbot register --email admin@example.com --agree-tos

# 查看证书详细信息
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -text -noout

# 查看到期日
echo | openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -dates

7.2 证书文件结构

文件名 说明
cert.pem 仅服务器证书(单独)
chain.pem 中间 CA 证书链
fullchain.pem 服务器证书 + 中间链(用于 Nginx/Apache)
privkey.pem 私钥(绝对不能泄露!)
安全警告:privkey.pem 绝对不要上传到 Git 仓库、共享文件夹或外部服务。一旦泄露,必须立即重新签发证书并吊销(revoke)原有证书。

8. HTTPS 配置验证与优化

8.1 在线 SSL 检测工具

# 使用 testssl.sh 本地检测
git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh https://example.com

# 查看证书链
curl -vI https://example.com 2>&1 | grep -i "SSL\|certificate"

# 查看协议/加密套件
nmap --script ssl-enum-ciphers -p 443 example.com

8.2 争取 A+ 等级的额外配置

# 生成 Strong DH Parameters(比较耗时)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

# 在 Nginx 配置中添加
ssl_dhparam /etc/nginx/dhparam.pem;

# 安全响应头(SSL Labs A+ 要求)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

8.3 HSTS Preload 注册

注册到 HSTS preload 后,即使用户首次访问你的网站,浏览器也会强制使用 HTTPS 连接:

# 注册条件(需全部满足)
1. 拥有有效证书
2. HTTP → HTTPS 重定向
3. 所有子域名均提供 HTTPS
4. HSTS 响应头中 max-age=63072000(2 年)以上
5. HSTS 响应头中包含 includeSubDomains
6. HSTS 响应头中包含 preload

# 注册网址:https://hstspreload.org/

9. 常见问题与解决方法

9.1 "Connection refused" 错误

Problem: Failed authorization procedure.
example.com (http-01): urn:ietf:params:acme:error:connection :: The server could not connect to the client to verify the domain :: Fetching http://example.com/.well-known/acme-challenge/xxx: Connection refused

原因:80 端口从外部无法访问
解决

  • 在防火墙中允许 80 端口:sudo ufw allow 80/tcp
  • 在云平台安全组中放行 HTTP
  • 确认域名解析到正确的 IP:dig example.com

9.2 Rate Limit 超限

Problem: too many certificates already issued for exact set of domains

原因:达到 Let's Encrypt 的签发限制
Let's Encrypt 的主要 Rate Limit

  • 相同域名集合每周 5 次重复签发限制
  • 同一注册域名每周 50 份证书
  • 失败的验证尝试每小时 5 次

解决

  • 测试环境使用 --staging 选项(无限制)
  • 等待一段时间后重试(通常 1 周)
  • 添加域名以改变域名集合
# 在 staging 环境中测试
sudo certbot --nginx --staging -d example.com

# staging 证书在实际浏览器中不会被信任
# 配置完成后正式签发:
sudo certbot --nginx --force-renewal -d example.com

9.3 DNS 验证失败

Problem: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.example.com

原因:DNS TXT 记录尚未传播
解决

# 检查 TXT 记录
dig -t txt _acme-challenge.example.com

# 通过 Google DNS 查询
dig -t txt _acme-challenge.example.com @8.8.8.8

# 通过 Cloudflare DNS 查询
dig -t txt _acme-challenge.example.com @1.1.1.1

# 全部返回相同值则表示传播完成

9.4 混合内容(Mixed Content)警告

在 HTTPS 页面中加载 HTTP 资源(图片、脚本等)时,浏览器会发出警告。

解决

  • 将 HTML 中的 http:// 改为 //(协议相对)或 https://
  • 通过 CSP 响应头自动升级:upgrade-insecure-requests
# 在 Nginx 中自动升级
add_header Content-Security-Policy "upgrade-insecure-requests" always;

9.5 证书续期失败

# 查看续期日志
sudo journalctl -u certbot.timer
sudo cat /var/log/letsencrypt/letsencrypt.log

# 常见原因
# 1. Web 服务器配置错误 → nginx -t / apachectl configtest
# 2. 防火墙拦截 → ufw status / firewall-cmd --list-all
# 3. 磁盘已满 → df -h
# 4. DNS 变更 → dig example.com

# 手动强制续期
sudo certbot renew --force-renewal --cert-name example.com

10. 实战运维检查清单

10.1 证书签发后的确认事项

  • 浏览器访问确认https://example.com 是否无警告打开
  • 锁形图标确认:地址栏是否显示绿色锁形图标
  • HTTP 重定向确认http://example.com 是否自动跳转到 HTTPS
  • SSL Labs 等级确认:至少 A 等级,争取 A+
  • 自动续期测试certbot renew --dry-run 是否成功
  • 证书到期监控:设置到期前 30 天提醒
  • 备份:定期备份 /etc/letsencrypt/ 目录

10.2 证书监控脚本

#!/bin/bash
# /usr/local/bin/check_ssl_expiry.sh - 证书到期监控

DOMAINS=("example.com" "api.example.com" "blog.example.com")
WARNING_DAYS=30
ALERT_EMAIL="admin@example.com"

for domain in "${DOMAINS[@]}"; do
    expiry_date=$(echo | openssl s_client -servername "$domain" \
        -connect "$domain:443" 2>/dev/null | \
        openssl x509 -noout -enddate | cut -d= -f2)

    expiry_epoch=$(date -d "$expiry_date" +%s)
    now_epoch=$(date +%s)
    days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

    echo "$domain: $days_left days remaining"

    if [ $days_left -lt $WARNING_DAYS ]; then
        echo "WARNING: $domain expires in $days_left days!" | \
            mail -s "SSL Expiry Alert: $domain" "$ALERT_EMAIL"
    fi
done
# 授予执行权限并注册 cron
sudo chmod +x /usr/local/bin/check_ssl_expiry.sh

# 每天上午 9 点执行
sudo crontab -e
# 0 9 * * * /usr/local/bin/check_ssl_expiry.sh

结论:用 Let's Encrypt 为所有网站启用 HTTPS

Let's Encrypt 不只是一个免费 SSL 证书的概念,更是一次将整个 Web 安全水平提升到新高度的创新。过去因成本问题而放弃 HTTPS 的众多网站,如今都已全部以安全的 HTTPS 对外提供服务,这对用户和服务提供者来说都是双赢的改变。

本指南涉及的核心要点总结如下:

  • 用 Certbot 实现自动化 - 手动签发和续期容易出错。请务必启用 Certbot 的自动续期。
  • 通配符使用 DNS-01 - 如果运营多个子域名,通配符证书要方便得多。
  • 一定要配置监控 - 即使设置了自动续期也可能失败,到期监控是必不可少的。
  • 以 SSL Labs A+ 等级为目标 - 不只是简单启用 HTTPS,还要同时应用优化的 SSL 配置。
  • HSTS 要谨慎启用 - 一旦启用就难以撤回,请在所有配置都完美时再启用。
  • 严格保护私钥 - privkey.pem 绝对不能泄露到外部。

使用 Let's Encrypt 和 Certbot,几分钟内即可构建生产级 HTTPS。请将本指南中的示例按实际环境加以调整,打造安全的 Web 服务。