• home > tools > webServer > nginx >

    https域名证书:Certbot申请和管理Let’s Encrypt证书方案

    Author:zhoulujun Date:

    Reddit sysadmin 社区甚至直接说:不要为 SSL 付钱,免费证书已经足够。

    之前 阿里与腾讯云提供的第三方免费域名证书,都能用一年,现在基本回归到一个季度(90天)了。


    Let's Encrypt 是一个非营利性组织(Internet Security Research Group, ISRG),它的核心商业模式是“不盈利”。它不通过销售证书赚钱,而是通过接受捐赠和赞助来维持运营。

    Let's Encrypt证书优势

    Let’s Encrypt 的目标就是让 HTTPS 成为默认标准,因此证书完全免费并自动化发行。

    • 完全免费

    • 自动签发 + 自动续期

    • 被所有浏览器信任

    • 支持:

      • 单域名

      • 多域名

      • 通配符证书(*.example.com)

    所以采用Let's Encrypt,是我这种草根网站的解决方案。

    当然如果选择  Cloudflare 免费 SSL方案也是可以的,但是你最好还是先申请Let's Encrypt证书打底:Cloudflare + Let's Encrypt 同时使用

    ✅ Let's Encrypt(服务器真实证书)

    ✅ Cloudflare(外层代理)

    普通 HTTPS 和免费证书安全性完全一样。

    Reddit sysadmin 社区甚至直接说:不要为 SSL 付钱,免费证书已经足够

    Let's Encrypt证书布署

    像阿里云、腾讯云等国内云厂商合作的域名证书供应商FreeSSL 、 TrustAsia等,都要不断地去申请,非常麻烦!

    ——可能目的就是要让交钱!因为,不是大企业做官网,我根本不需要啥 OV(企业验证)

    真正可长期运行的生产级方案,肯定是自动申请、自动续期的方案,比如:

    • Certbot:最老牌、文档最全的工具,支持几乎所有 Linux 发行版。

    • acme.sh:轻量级 Shell 脚本,功能强大,支持多种 DNS API 自动验证,国内用户使用较多。

    • Nginx Proxy Manager / 1Panel / 宝塔面板:这些可视化管理面板内置了 ACME 客户端,点几下鼠标即可自动申请和续期。

    设计思路(先理解)

    读取 nginx 配置

        ↓

    解析 server_name

        ↓

    检测是否已有证书

        ↓

    没有 → 自动申请

    已有 → 自动续期

        ↓

    安装证书

        ↓

    reload nginx


    acme.sh

    acme.sh 本身已经支持自动续期。所以外贸需要写个脚本负责:发现新域名并自动签发。

    自动证书脚本

    #!/bin/bash
    set -euo pipefail  # 增强错误检测,避免脚本静默失败
    
    # ================= 配置区域(适配你的实际环境)=================
    NGINX_CONF_DIR="/etc/nginx"
    # 改为你实际的证书目录:/etc/nginx/ssl_certificate/
    SSL_BASE_DIR="/etc/nginx/ssl_certificate"
    ACME_SH="$HOME/.acme.sh/acme.sh"
    LOG_FILE="/var/log/auto_ssl_update.log"
    # DNS 提供商设置 ( dns_ali=阿里云, dns_tc=腾讯云, dns_cf=Cloudflare )
    DNS_PROVIDER="dns_ali"
    # 你的邮箱(用于ACME账户注册和证书过期提醒)
    ACME_EMAIL="[email protected]"
    # ===========================================
    
    # 加载环境变量 (API Keys)
    if [ -f /etc/profile.d/acme_env.sh ]; then
        source /etc/profile.d/acme_env.sh
    else
        echo "错误: 未找到 API 环境变量文件 /etc/profile.d/acme_env.sh" | tee -a "$LOG_FILE"
        exit 1
    fi
    
    # 检查 acme.sh
    if [ ! -x "$ACME_SH" ]; then
        echo "错误: acme.sh 未找到或未执行权限: $ACME_SH" | tee -a "$LOG_FILE"
        exit 1
    fi
    
    # 创建证书目录并检查权限
    mkdir -p "$SSL_BASE_DIR"
    if [ ! -w "$SSL_BASE_DIR" ]; then
        echo "错误: 无写入权限到 $SSL_BASE_DIR" | tee -a "$LOG_FILE"
        exit 1
    fi
    
    # 日志函数
    log() {
        echo "[$(date '+%F %T')] $1" | tee -a "$LOG_FILE"
    }
    
    log "===== 开始自动 SSL 证书维护 ====="
    
    # 1. 解析 Nginx 配置(修复include/注释问题,适配你的配置)
    log "正在解析 Nginx 配置文件(包含include)..."
    # 先拼接所有Nginx配置内容(解决include嵌套)
    nginx_config_content=$(find "$NGINX_CONF_DIR" -name "*.conf" -type f -exec cat {} \;)
    # 解析server块,过滤注释/无效域名,按组输出
    grouped_domains=$(echo "$nginx_config_content" | awk '
        BEGIN { in_server=0; names="" }
        # 跳过注释行(// 或 # 开头)
        /^[[:space:]]*\/\// || /^[[:space:]]*#/ { next }
        # 移除行内注释
        { gsub(/\/\/.*$/, ""); gsub(/#.*$/, "") }
        # 进入server块
        /server\s*\{/ { in_server=1; names="" }
        # 提取server_name,过滤空值/下划线/localhost
        in_server && /server_name/ { 
            gsub(/server_name/, ""); 
            gsub(/;/, ""); 
            for(i=1; i<=NF; i++) {
                if($i != "" && $i !~ /^_$/ && $i !~ /localhost/) {
                    names = names " " $i
                }
            }
        }
        # 退出server块,输出域名组
        in_server && /^\s*\}/ { 
            if(names != "") {
                gsub(/^ +| +$/, "", names)
                print names
            }
            in_server=0 
        }
    ')
    
    # 检查是否解析到域名
    if [ -z "$grouped_domains" ]; then
        log "未在 Nginx 配置中发现有效的 server_name 域名。"
        exit 0
    fi
    
    # 打印解析结果(便于调试)
    log "解析到的域名组:"
    echo "$grouped_domains" | while read -r line; do
        log "  - $line"
    done
    
    # 初始化acme.sh(首次运行需要,设置正式Let's Encrypt服务器)
    log "初始化acme.sh配置..."
    "$ACME_SH" --set-default-ca --server letsencrypt >> "$LOG_FILE" 2>&1
    "$ACME_SH" --register-account -m "$ACME_EMAIL" --agree-tos >> "$LOG_FILE" 2>&1
    
    # 2. 遍历每一组域名申请/更新证书
    echo "$grouped_domains" | while IFS= read -r domain_line; do
        [ -z "$domain_line" ] && continue
        
        # 获取主域名(组内第一个)
        main_domain=$(echo "$domain_line" | awk '{print $1}')
        log "处理域名组: [$domain_line] (主域名: $main_domain)"
        
        # 创建证书目录(适配你的路径:/etc/nginx/ssl_certificate/主域名/)
        CERT_DIR="$SSL_BASE_DIR/$main_domain"
        mkdir -p "$CERT_DIR"
        chmod 700 "$CERT_DIR"  # 安全权限:仅root可访问
        
        # 构建acme.sh命令(用数组替代eval,避免安全风险)
        ACME_CMD=(
            "$ACME_SH"
            --issue
            --dns "$DNS_PROVIDER"
            --log "$LOG_FILE"
            --log-level 2
            --force-renew 0  # 仅证书快过期时续期(默认60天)
        )
        # 添加所有域名到命令参数
        for d in $domain_line; do
            ACME_CMD+=("-d" "$d")
        done
        
        # 执行证书申请/更新
        if "${ACME_CMD[@]}"; then
            log "成功:$main_domain 证书已更新/无需更新"
            
            # 部署证书(适配你的Nginx配置文件名:xxx_bundle.crt / xxx.key)
            deploy_success=0
            if "$ACME_SH" --install-cert -d "$main_domain" \
                --fullchain-file "$CERT_DIR/${main_domain}_bundle.crt" \  # 匹配你的bundle.crt
                --key-file       "$CERT_DIR/${main_domain}.key" \          # 匹配你的.key
                --reloadcmd      "systemctl reload nginx" >> "$LOG_FILE" 2>&1; then
                
                # 设置证书文件权限(Nginx要求只读,避免权限过大)
                chmod 600 "$CERT_DIR/${main_domain}_bundle.crt"
                chmod 600 "$CERT_DIR/${main_domain}.key"
                deploy_success=1
            fi
            
            if [ $deploy_success -eq 1 ]; then
                log "部署成功:$main_domain 证书已替换到 $CERT_DIR"
            else
                log "错误:$main_domain 证书部署失败"
            fi
        else
            log "错误:$main_domain 证书申请/更新失败"
        fi
    done
    
    log "===== 自动 SSL 证书维护结束 ====="


    Certbot 

    Certbot 比 acme.sh 简单太多,而且更稳、更省心。

    • Nginx

    • 多域名

    • 有各种 include

    • 希望自动读域名 → 自动申请证书 → 自动替换 → 自动续期

    方式难度是否需要自己解析 Nginx是否需要处理 DNS/API是否自动续期
    Certbot + Nginx 插件⭐ 极简单自动不需要全自动
    acme.sh + 你写的脚本⭐⭐⭐⭐⭐ 复杂需要自己写 awk需要配置 DNS API要自己写定时

    CentOS Stream 9 + Certbot + 腾讯云泛域名

    # 1. 安装 Certbot (如果未安装)
    sudo dnf install -y certbot python3-certbot-nginx
    
    # 2. 安装腾讯云 DNS 插件 (关键步骤)
    # 注意:插件包名通常是 certbot-dns-tencentcloud
    pip3 install certbot-dns-tencentcloud
    
    # 验证插件是否安装成功
    certbot plugins | grep tencent
    # 应该能看到 dns-tencentcloud 字样
    1. 登录 腾讯云控制台。

    2. 点击右上角头像 -> 访问管理 -> 访问密钥 -> API 密钥管理。

    3. 新建密钥,获取 SecretId 和 SecretKey。

    4. 重要:确保该密钥关联的 CAM 用户拥有 DNSPodFullAccess 或 QcloudDNSFullAccess 权限(否则无法添加 TXT 记录)。

    为了安全,不要直接在命令行输入密钥,而是创建一个配置文件。

    sudo mkdir -p /etc/letsencrypt/credentials
    sudo vi /etc/letsencrypt/credentials/tencent-creds.ini

    填入以下内容(替换为你的真实密钥):

    dns_tencentcloud_secret_id = 你的SecretId
    dns_tencentcloud_secret_key = 你的SecretKey

    设置严格权限(非常重要,否则 Certbot 会报错拒绝运行):

    sudo chmod 600 /etc/letsencrypt/credentials/tencent-creds.ini

    使用以下命令申请 zhoulujun.cn 的泛域名证书(包含主域名和所有子域名):

    sudo certbot certonly \
      --dns-tencentcloud \
      --dns-tencentcloud-credentials /etc/letsencrypt/credentials/tencent-creds.ini \
      -d zhoulujun.cn \
      -d "*.zhoulujun.cn" \
      --agree-tos \
      --non-interactive \
      --email [email protected]

    证书生成后,通常位于 /etc/letsencrypt/live/zhoulujun.cn/。

    修改你的 Nginx 配置指向这些文件:

    ssl_certificate /etc/letsencrypt/live/zhoulujun.cn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/zhoulujun.cn/privkey.pem;

    Certbot 自带续期命令。由于使用了 DNS 插件,续期时也需要指定凭证文件。

    sudo crontab -e

    添加以下行(每天凌晨 3 点检查并续期,如需重载 nginx 可加 --deploy-hook):

    0 3 * * * /usr/bin/certbot renew --dns-tencentcloud --dns-tencentcloud-credentials /etc/letsencrypt/credentials/tencent-creds.ini --quiet --post-hook "systemctl reload nginx"


    当然,Let's Encrypt 单张证书支持最多 100 个域名,完全可以把 zhoulujun.cn、zhoulujun.com、zhoulujun.net 这三个域名的泛域名(包括主域名)都打包到同一张 Let's Encrypt 证书里,这是 Let's Encrypt 明确支持的,且对你的场景来说更省心(少管理多张证书)。

    但是,这里面有个坑:

    如果你把多个域名绑在同一张证书里,只要其中一个域名失效、DNS 不可用、解析不对,整张证书就申请 / 续期失败 → 所有域名 HTTPS 全部挂掉。

    Let’s Encrypt 规则:一张证书里的所有域名,必须全部验证通过,证书才能颁发 / 续期

    最安全、最稳定、最适合你的方案:每个主域名,单独一张泛域名证书



    转载本站文章《https域名证书:Certbot申请和管理Let’s Encrypt证书方案》,
    请注明出处:https://www.zhoulujun.cn/html/tools/webServer/nginx/2026_0228_9768.html