群晖SynologyNAS DSM通过 acme.sh 实现域名证书自动更新

sunfeng 2025-08-13 12:43 阅读数 84 #synology

注意事项:

①尽量使使用root帐号去操作,管理员帐号可能会出现各种报错。

②群晖默认证书保存目录:  

/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT)

默认情况下群晖的默认证书是官方的证书,acme导入证书前一定先手动导入一次证书。

③阿里与腾讯及其他域名供应商的 dns api 参数不一样,具体可参考 Acme.sh 官方文档 中的「DNS API」列表。

https://github.com/acmesh-official/acme.sh/wiki/dnsapi

④准备申请证书的域名一定先去 阿里云腾讯云(DNSPod) 看下有没有解析记录,新申请的域名或者从没有使用的二级域名会申请失败。

一、准备工作

登录 阿里云腾讯云(DNSPod) 创建并获取API密钥保存下来。

二、使用ssh登录群晖

1.下载acme.sh

acme.sh 是一个用于自动化获取和管理 Let’s Encrypt 证书的脚本。

curl https://get.acme.sh | sh -s email=1396053199@qq.com --force
或
wget -O -  https://get.acme.sh | sh -s email=1396053199@qq.com --force

将 1396053199@qq.com 替换为自己的有效电子邮件地址,用于接收 Let’s Encrypt 的重要通知,如证书即将过期等信息。

安装过程会有报错,群晖系统默认没有安装或没有权限启用 cron 服务(定时任务工具),而 Acme.sh 依赖 cron 实现证书自动续签。

可以忽略这个报错,最后出现 Install success 就可以。acme.sh 会自动添加到系统环境变量中。

如果成功安装,脚本会将 acme.sh 下载到当前用户目录下,也就是 ~/.acme.sh 目录。

通过 cd ~/.acme.sh 或 cd /var/services/homes/当前登录的用户名/.acme.sh 进入目录

使用命令验证 acme.sh 是否安装成功:

cd ~/.acme.sh    #或 cd /var/services/homes/sunfeng/.acme.sh “sunfeng”为我当前登录的用户名
./acme.sh --version

2. 添加API密钥至/etc/profile

#阿里云 API 密钥添加到环境变量
echo "export Ali_Key="AccessKey ID"" >> /etc/profile
echo "export Ali_Secret="AccessKey Secret"" >> /etc/profile && source /etc/profile

# 腾讯云 DNSPod 的 API 密钥添加到环境变量
echo "export DP_Id="AccessKey ID"" >> /etc/profile
echo "export DP_Key="AccessKey Token"" >> /etc/profile && source /etc/profile
# 删除包含"export DP_Id="的行
sudo sed -i '/export DP_Id=/d' /etc/profile

# 删除包含"export DP_Key="的行
sudo sed -i '/export DP_Key=/d' /etc/profile


3.申请证书,填写的域名一定是阿里、腾讯或其他域名供应商的管理页面上有过解析记录

./acme.sh --issue --dns dns_ali -d example.com -d *.example.com

./acme.sh                 # 使用 acme.sh 脚本执行证书申请操作
  --issue                 # 核心参数:表示证书申请流程
  --dns dns_ali           # 指定 DNS 验证方式,这里使用阿里云(Aliyun)的 DNS 解析接口
  -d example.com           # 为主域名 example.com 申请证书
  -d *.example.com         # 同时为该域名的所有子域名(通配符)申请证书

证书申请成功后会保存在 ~/.acme.sh (/root/.acme.sh/) 目录下

4、查看已安装证书信息:

acme.sh --info -d example.com    # example.com 改为自己的域名

三、替换群晖NAS的默认证书

新申请的证书保存名录:

# ls /root/.acme.sh/ 可以看到多了一个example.com 或者 example.com_ecc的目录 此目录下就是新申请的证书
/root/.acme.sh/example.com_ecc/

群晖默认证书保存目录:  

/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT)
sudo cp -f "/root/.acme.sh/example.com_ecc/fullchain.cer" "/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT)/cert.pem"
sudo cp -f "/root/.acme.sh/example.com_ecc/hkcnas.com.key" "/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT)/privkey.pem"
sudo cp -f "/root/.acme.sh/example.com_ecc/ca.cer" "/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT)/chain.pem"
  1. 路径格式
    • 源路径 /root/.acme.sh/hkcnas.com_ecc/ 是 ECC 证书的正确存放目录。

    • 目标路径使用了 $(cat /usr/syno/etc/certificate/_archive/DEFAULT) 动态获取群晖默认证书的 ID 目录,能自动定位当前正在使用的证书目录,无需手动填写 ID。

  2. 文件名映射
    • fullchain.cer → cert.pem(群晖要求的证书文件名)

    • hkcnas.com.key → privkey.pem(群晖要求的私钥文件名)

    • ca.cer → chain.pem(群晖要求的中间证书文件名)
      文件名映射完全符合群晖系统对证书文件的命名规范,确保服务能正确识别。

  3. 参数 -f
            强制覆盖目标文件(如果已存在),避免因文件已存在而复制失败,适合证书更新场景。

然后重启 Nginx 即可看到证书已更新

nginx -s reload

四、设置自动更新

acme 申请证书每 60 天自动更新,默认情况下你无需任何操作,但是最开始下载安装 acme 时 cron 服务报错,不确定是否可以自动更新。

acme可以强制续签证书:

acme.sh --renew -d example.com --force    # example.com 改为自己的域名

!!!修改下面脚本中的 DOMAIN="example.com" 与 DNS_MODE="dns_ali"

未检测到证书:强制执行续签

证书已过期(剩余 0 天):强制执行续签

证书剩余有效期≤10 天:强制执行续签

证书剩余有效期 > 10 天:不执行任何操作(无论是否超过 20 天)

#!/bin/bash
set -e  # 遇到错误立即退出

# ==================== 配置参数 ====================
DOMAIN="example.com"                  # 你的域名
ACME_CERT_PATH="/root/.acme.sh"       # acme.sh证书存放路径
SYNO_CERT_ARCHIVE="/usr/syno/etc/certificate/_archive"  # 群晖证书存档目录
DNS_MODE="dns_ali"                    # DNS验证方式(根据你的服务商修改 https://github.com/acmesh-official/acme.sh/wiki/dnsapi)
CERT_TYPE="ecc"                       # 证书类型(ecc/rsa,留空自动适配)
RENEW_THRESHOLD=10                    # 少于此天数时强制续签(天)
# =================================================

# 函数:显示信息
info() {
    echo -e "\033[1;34m=== $1 ===\033[0m"
}

# 函数:显示成功信息
success() {
    echo -e "\033[1;32m=== $1 ===\033[0m"
}

# 函数:显示错误信息
error() {
    echo -e "\033[1;31m=== 错误:$1 ===\033[0m"
    exit 1
}

# 函数:检查证书剩余有效期(天)
check_cert_validity() {
    local cert_file=$1
    if [ ! -f "$cert_file" ]; then
        echo 0
        return
    fi

    # 获取证书过期时间(Unix时间戳)
    local end_date=$(openssl x509 -in "$cert_file" -noout -enddate | cut -d= -f2)
    local end_timestamp=$(date -d "$end_date" +%s 2>/dev/null || true)
    local current_timestamp=$(date +%s)

    if [ -z "$end_timestamp" ] || [ "$end_timestamp" -le "$current_timestamp" ]; then
        echo 0  # 证书已过期
        return
    fi

    # 计算剩余天数
    local diff_seconds=$((end_timestamp - current_timestamp))
    local diff_days=$((diff_seconds / 86400))  # 86400秒=1天
    echo $diff_days
}

# 1. 检查证书是否已存在(支持RSA和ECC)
info "检查证书是否已存在"
CERT_DIR_RSA="$ACME_CERT_PATH/$DOMAIN"
CERT_DIR_ECC="$ACME_CERT_PATH/${DOMAIN}_ecc"
CERT_EXISTS=0
CURRENT_CERT_DIR=""
CERT_FILE=""

# 优先检查配置的证书类型
if [ "$CERT_TYPE" = "ecc" ] && [ -d "$CERT_DIR_ECC" ]; then
    CURRENT_CERT_DIR="$CERT_DIR_ECC"
    CERT_FILE="$CURRENT_CERT_DIR/fullchain.cer"
    CERT_EXISTS=1
elif [ "$CERT_TYPE" = "rsa" ] && [ -d "$CERT_DIR_RSA" ]; then
    CURRENT_CERT_DIR="$CERT_DIR_RSA"
    CERT_FILE="$CURRENT_CERT_DIR/fullchain.cer"
    CERT_EXISTS=1
# 未指定类型时自动检测
elif [ -z "$CERT_TYPE" ] && [ -d "$CERT_DIR_ECC" ]; then
    CURRENT_CERT_DIR="$CERT_DIR_ECC"
    CERT_FILE="$CURRENT_CERT_DIR/fullchain.cer"
    CERT_TYPE="ecc"
    CERT_EXISTS=1
elif [ -z "$CERT_TYPE" ] && [ -d "$CERT_DIR_RSA" ]; then
    CURRENT_CERT_DIR="$CERT_DIR_RSA"
    CERT_FILE="$CURRENT_CERT_DIR/fullchain.cer"
    CERT_TYPE="rsa"
    CERT_EXISTS=1
fi

if [ $CERT_EXISTS -eq 1 ]; then
    echo "发现已存在的$CERT_TYPE证书:$CURRENT_CERT_DIR"
    
    # 检查证书剩余有效期
    info "检查证书剩余有效期"
    REMAIN_DAYS=$(check_cert_validity "$CERT_FILE")
    echo "证书剩余有效期:$REMAIN_DAYS 天"
    
    # 根据剩余天数执行不同操作
    if [ $REMAIN_DAYS -eq 0 ]; then
        echo "证书已过期,将强制执行续签"
    elif [ $REMAIN_DAYS -le $RENEW_THRESHOLD ]; then
        echo "证书剩余有效期不足$RENEW_THRESHOLD天,将执行续签"
    else
        success "证书有效期充足(剩余$REMAIN_DAYS天),无需更新"
        exit 0
    fi
else
    echo "未发现已存在的证书,将执行新申请"
    # 未指定类型时默认使用ECC
    if [ -z "$CERT_TYPE" ]; then
        CERT_TYPE="ecc"
        echo "默认使用ECC证书类型"
    fi
fi

# 2. 生成证书命令参数
info "准备证书更新/申请命令"
ACME_CMD_BASE="$ACME_CERT_PATH/acme.sh"
DOMAIN_PARAMS="-d $DOMAIN -d *.${DOMAIN}"
FORCE_PARAM="--force"
DNS_PARAM="--dns $DNS_MODE"
CERT_TYPE_PARAM=""
if [ "$CERT_TYPE" = "ecc" ]; then
    CERT_TYPE_PARAM="--ecc"
fi

# 3. 执行更新或申请
info "执行证书操作($([ $CERT_EXISTS -eq 1 ] && echo "续签" || echo "新申请"))"
if [ $CERT_EXISTS -eq 1 ]; then
    # 证书已存在,执行续签
    if ! $ACME_CMD_BASE --renew $DOMAIN_PARAMS $FORCE_PARAM $DNS_PARAM $CERT_TYPE_PARAM; then
        error "证书续签失败,请检查acme.sh配置和网络"
    fi
else
    # 证书不存在,执行新申请
    if ! $ACME_CMD_BASE --issue $DOMAIN_PARAMS $FORCE_PARAM $DNS_PARAM $CERT_TYPE_PARAM; then
        error "证书申请失败,请检查acme.sh配置和网络"
    fi
    # 申请后更新证书目录变量
    if [ "$CERT_TYPE" = "ecc" ]; then
        CURRENT_CERT_DIR="$CERT_DIR_ECC"
    else
        CURRENT_CERT_DIR="$CERT_DIR_RSA"
    fi
    CERT_FILE="$CURRENT_CERT_DIR/fullchain.cer"
fi

# 4. 验证证书文件是否存在
info "验证证书文件完整性"
REQUIRED_FILES=(
    "$CURRENT_CERT_DIR/fullchain.cer"
    "$CURRENT_CERT_DIR/$DOMAIN.key"
    "$CURRENT_CERT_DIR/ca.cer"
)

for file in "${REQUIRED_FILES[@]}"; do
    if [ ! -f "$file" ]; then
        error "缺少必要的证书文件:$file"
    fi
done

# 5. 复制证书到群晖默认目录并替换
info "复制证书到群晖系统目录"
# 获取群晖当前默认证书ID
CERT_ID=$(cat "$SYNO_CERT_ARCHIVE/DEFAULT" 2>/dev/null || true)
if [ -z "$CERT_ID" ]; then
    error "无法获取群晖默认证书ID,请检查证书目录"
fi

DEST_DIR="$SYNO_CERT_ARCHIVE/$CERT_ID"
if [ ! -d "$DEST_DIR" ]; then
    error "群晖证书目标目录不存在:$DEST_DIR"
fi

# 强制复制并覆盖
echo "复制证书到目标目录:$DEST_DIR"
cp -f "$CURRENT_CERT_DIR/fullchain.cer" "$DEST_DIR/cert.pem" || error "复制证书文件失败"
cp -f "$CURRENT_CERT_DIR/$DOMAIN.key" "$DEST_DIR/privkey.pem" || error "复制私钥文件失败"
cp -f "$CURRENT_CERT_DIR/ca.cer" "$DEST_DIR/chain.pem" || error "复制CA证书失败"

# 6. 重启相关服务确保生效
info "重启服务使证书生效"
if ! nginx -s reload; then
    error "Nginx重启失败,请手动检查"
fi


success "所有操作完成!证书已$( [ $CERT_EXISTS -eq 1 ] && echo "更新" || echo "安装" )并生效"

复制上面的代码并修改 “example.com”“dns_ali”另存为 “update_synology_cert.sh”(文件名可以随便定义)

或者直接 下载 我的脚本修改 example.com”“dns_aliupdate_synology_cert.sh

然后上传至群晖个人home目录下(目录自己随意选择),记住并复制“位置”信息。

打开计划任务:控制面板→计划任务→新增→用户自定义的脚本

用户帐号一定选择 “root”,

然后任务设置→用户自定义的脚本输入:

cd /volume1/homes/sunfeng/
chmod +x update_synology_cert.sh
./update_synology_cert.sh


保存计划任务即可,以后就不用操心证书更新问题了


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

搜索
标签列表