Skip to content

HTTPS 证书

为什么要自己配置

各大云厂商(阿里云、腾讯云等)提供的免费证书有效期只有 90 天,到期后需要手动登录控制台 → 重新申请 → 下载 → 上传到服务器 → 重启 Nginx,流程繁琐且容易忘记。

Let's Encrypt 是一个免费、自动化的证书颁发机构(CA),配合 Certbot 工具,可以实现:

  • 一行命令申请证书
  • 系统定时任务自动续签(到期前 30 天自动处理)
  • 续签后自动重载 Nginx

配好一次后完全无需人工介入。

前提条件

  • 已有一个域名,并将 DNS A 记录解析到服务器 IP
  • 云控制台安全组已放行 80(HTTP)和 443(HTTPS)端口

安装 Certbot

先确认当前系统,再按对应步骤操作:

bash
cat /etc/os-release | grep -E "^(NAME|VERSION_ID|ID)"

根据输出判断系统:

输出关键字对应系统使用哪个安装方式
NAME="CentOS Linux" / AlmaLinuxCentOS / AlmaLinux(RHEL 8 系)👇 方式一
NAME="Alibaba Cloud Linux" / NAME="Anolis OS"阿里云 Linux / Anolis👇 方式二
NAME="Ubuntu" / NAME="Debian"Ubuntu / Debian👇 方式三

不确定时再看 Python 版本,Python 3.9 = RHEL 8 系,Python 3.11 = RHEL 9 系(即方式二)

bash
python3 --version

CentOS / AlmaLinux(RHEL 8 系):

bash
# 1. 安装 EPEL 源
yum install epel-release -y

# 2. 安装 Certbot 和 Nginx 插件
yum install certbot python3-certbot-nginx -y

# 3. 验证
certbot --version

Alibaba Cloud Linux 4 / Anolis OS(RHEL 9 系):

该系统自带 Python 3.11,而 EPEL 仓库的 Certbot 依赖 Python 3.9,直接 dnf install 会报依赖冲突。需要用 pip 安装

bash
# 1. 安装 pip
dnf install python3-pip -y

# 2. 通过 pip 安装 Certbot 和 Nginx 插件
pip3 install certbot certbot-nginx

# 3. 验证
certbot --version

如果 pip 安装后 certbot 命令找不到,运行 ln -s /usr/local/bin/certbot /usr/bin/certbot 创建软链接


Ubuntu / Debian:

bash
# 默认仓库已包含,直接安装
apt install certbot python3-certbot-nginx -y

# 验证
certbot --version

备选方案:snap 安装(适用于所有 Linux)

如果上述方式都遇到问题,snap 安装完全独立于系统 Python,不会有依赖冲突:

bash
# 1. 安装 snapd
dnf install snapd -y         # CentOS / RHEL 系
apt install snapd -y         # Ubuntu / Debian

# 2. 启用 snap
systemctl enable --now snapd.socket
ln -s /var/lib/snapd/snap /snap

# 3. 安装 Certbot
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot

# 4. 验证
certbot --version

Docker Nginx 配置流程

Nginx 跑在容器里时,不能直接使用 --nginx 插件(插件需要在宿主机上找到并修改 nginx.conf),需要在宿主机上运行 Certbot,借用容器内的 Nginx 完成 HTTP-01 验证。

--webroot 是什么:Certbot 不自己启动任何服务,而是把临时验证文件写入 -w 指定的目录,由已在运行的 Nginx 直接提供该文件的 HTTP 访问。Let's Encrypt 通过访问 http://your-domain/.well-known/acme-challenge/ 完成验证,整个过程 Nginx 不中断。"手动"指的是需要你提前在 Nginx 配置中写好 /.well-known/acme-challenge/ 的路由。

第一步:配置 docker-compose.yml

将证书目录和 webroot 验证目录挂载进 Nginx 容器:

yaml
services:
  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d # Nginx 配置
      - /etc/letsencrypt:/etc/letsencrypt:ro # 证书目录(只读)
      - /var/www/certbot:/var/www/certbot # webroot 验证目录
    restart: unless-stopped

第二步:配置 Nginx 允许 webroot 验证

在申请证书之前,Nginx 配置中只写 HTTP,还不能写 HTTPS(证书文件尚不存在,Nginx 会启动失败):

nginx
server {
    listen 80;
    server_name example.com www.example.com;

    # Let's Encrypt webroot 验证路径(必须保留)
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # 其他请求暂时正常响应即可,申请到证书后再改为跳转 HTTPS
    location / {
        return 200 "ok";
    }
}
bash
# 创建 webroot 目录并启动容器
mkdir -p /var/www/certbot
docker compose up -d

第三步:申请证书

宿主机上运行 Certbot,使用 webroot 模式借用 Nginx 容器完成验证:

bash
certbot certonly --webroot -w /var/www/certbot -d example.com -d www.example.com

-w 只控制临时验证文件的写入位置,验证完成后自动删除。证书始终保存在 /etc/letsencrypt/live/example.com/,与 -w 无关。容器通过卷挂载读取该目录。

第四步:更新 Nginx 配置启用 HTTPS

证书申请成功后,补充 HTTPS 配置(路径与宿主机直装 Nginx 完全相同,因为目录已挂载进容器):

nginx
server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    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;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /usr/share/nginx/html;
    index index.html;
}
bash
# 重载容器内 Nginx(不重启容器,不中断连接)
docker compose exec nginx nginx -s reload

第五步:配置续签 reload 钩子

deploy 钩子改为操作容器,注意要指定 docker compose 所在的项目目录:

bash
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh << 'EOF'
#!/bin/bash
# 改为你的 docker-compose.yml 实际路径
cd /path/to/your/project
docker compose exec nginx nginx -s reload
EOF

chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh

最后验证自动续签是否正常:

bash
certbot renew --dry-run

配置自动续签

通过系统包管理器(yum/apt)安装的 Certbot 会自动注册 systemd timer:

bash
# 确认 timer 已启用
systemctl status certbot.timer

# 若未启用,手动开启
systemctl enable --now certbot.timer

# 查看下次执行时间
systemctl list-timers | grep certbot

通过 snap 安装的 Certbot,timer 名称不同,同样自动注册无需额外配置:

bash
systemctl status snap.certbot.renew.timer

通过 pip 安装的 Certbot 不会注册 systemd timer,需要手动配置 cron。

先确认 certbot 的实际路径:

bash
which certbot

然后添加 cron 任务:

bash
crontab -e  # 编辑 cron 任务列表

加入以下内容(使用 which certbot 得到的完整路径,避免 cron 环境找不到命令):

0 2 * * * /usr/bin/certbot renew --quiet

常用 crontab 参数:

参数含义
crontab -l查看当前 cron 任务列表
crontab -e编辑 cron 任务
crontab -r删除所有 cron 任务(慎用)

验证续签配置正常(不会真正续签):

bash
certbot renew --dry-run

续签钩子目录说明

钩子目录执行时机用途
pre/续签之前预处理(如备份)
deploy/续签成功之后重载 Nginx 使新证书生效(Docker 场景必须配)
post/续签完成后(无论成败)清理操作

DNS-01 验证模式(通配符证书)

HTTP-01 只能给具体域名签证书(example.comwww.example.com),不支持通配符。如果想用 *.example.com 覆盖所有子域名,需要用 DNS-01 验证

原理:Certbot 要求你在 DNS 中添加一条 TXT 记录,Let's Encrypt 通过查询 DNS 来验证域名所有权。

bash
# 手动 DNS 验证方式(需要手动去 DNS 控制台添加 TXT 记录)
certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d example.com

执行后会提示:

Please deploy a DNS TXT record under the name:
_acme-challenge.example.com
with the following value:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

去域名的 DNS 管理后台(阿里云 → 域名解析 → 添加记录):

记录类型主机记录记录值
TXT_acme-challengeCertbot 提示的那串值

添加后等 1~2 分钟 DNS 生效,回到终端按回车继续。

缺点:手动模式下每次续签都要手动改 DNS 记录,无法自动化。如果你的 DNS 服务商支持 API(如阿里云、Cloudflare),可以用 acme.sh 实现全自动。

acme.sh(轻量替代方案)

acme.sh 是纯 Shell 实现的 ACME 客户端,比 Certbot 更轻量,且原生支持各大 DNS 服务商 API,是通配符证书自动续签的最佳选择。

安装

bash
curl https://get.acme.sh | sh -s email=your@email.com

# 使脚本生效(或重新登录终端)
source ~/.bashrc

使用 DNS API 申请通配符证书(以阿里云为例)

先在阿里云创建 AccessKey(RAM 用户 → 创建用户 → 赋予 DNS 权限 → 获取 Key),然后:

bash
# 设置阿里云 DNS API 密钥
export Ali_Key="你的AccessKeyId"
export Ali_Secret="你的AccessKeySecret"

# 申请通配符证书(acme.sh 会自动添加和删除 DNS TXT 记录)
acme.sh --issue --dns dns_ali -d example.com -d "*.example.com"

安装证书到 Nginx(Docker 版)

bash
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.pem \
  --reloadcmd      "docker compose -f /path/to/your/project/docker-compose.yml exec nginx nginx -s reload"
bash
# 创建证书目录(宿主机上)
mkdir -p /etc/nginx/ssl

在 Nginx 配置中引用:

nginx
ssl_certificate     /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;

acme.sh 会自动注册 cron 定时任务,到期前自动续签并执行 --reloadcmd

bash
# 查看定时任务
crontab -l | grep acme.sh

# 手动测试续签
acme.sh --renew -d example.com --force

Certbot vs acme.sh

对比项Certbotacme.sh
安装方式系统包管理器一行 curl 脚本
依赖Python 运行时纯 Shell,无额外依赖
Nginx 集成官方插件,自动改配置手动指定证书路径
通配符证书需要手动 DNS 验证原生支持 DNS API,全自动
自动续签systemd timercron 定时任务
推荐场景单域名 + Nginx 环境通配符证书 / 多域名 / 无 Nginx

添加新子域名

配好初始证书后,如果需要给新子域名(如 api.example.com)也启用 HTTPS,不能复用现有证书文件——Let's Encrypt 签发的证书绑定了申请时指定的域名列表,新增域名需要重新向 Let's Encrypt 验证所有权、更新证书。

前提:新子域名的 DNS A 记录已解析到服务器 IP。


第一步: 重新申请证书,列出所有域名(含新增的)

bash
certbot certonly --webroot -w /var/www/certbot \
  -d example.com \
  -d www.example.com \
  -d api.example.com    # 新增的子域名

Certbot 检测到已有证书会询问是否扩展,选择 Expand

第二步: 在 Nginx 配置中为新子域名添加 server

nginx
server {
    listen 80;
    server_name api.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name api.example.com;

    # 证书与主域名共用同一份(扩展后已包含新子域名)
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # 按需配置:反向代理到后端服务
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

第三步: reload 容器内 Nginx

bash
docker compose exec nginx nginx -s reload

子域名多时的最佳实践

情况推荐方案
子域名固定、数量少(≤ 5 个)单独申请,逐条 -d 追加
子域名多或经常新增申请 *.example.com 通配符证书,新增子域名无需重签

通配符证书参考 DNS-01 验证模式 章节,一次覆盖所有子域名,新增子域名只需配置 Nginx,无需重新申请证书。

常用管理命令

bash
# === Certbot ===
certbot certificates                              # 查看所有证书及到期时间
certbot renew                                     # 续签所有临近到期的证书
certbot renew --dry-run                           # 模拟续签(验证配置是否正常)
certbot renew --cert-name example.com             # 续签指定域名
certbot delete --cert-name example.com            # 删除证书
journalctl -u certbot                             # 查看续签日志

# === acme.sh ===
acme.sh --list                                    # 查看所有证书及到期时间
acme.sh --renew -d example.com                    # 续签指定域名
acme.sh --renew -d example.com --force            # 强制续签(测试用)
acme.sh --remove -d example.com                   # 删除证书

证书吊销

证书吊销用于在证书泄露或不再需要时主动使其失效,Let's Encrypt 会将其加入吊销列表,浏览器访问时会报错。续签失败或迁移服务器时不需要吊销,直接续签或申请新证书即可。

bash
# 吊销证书(需指定证书文件路径)
certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem

# 吊销后删除本地证书文件(可选)
certbot delete --cert-name example.com

证书吊销后无法恢复,需重新申请。如果只是不再使用该域名,直接 certbot delete 删除本地文件即可,不必吊销。

持续学习,持续成长