之前阿里云和腾讯免费的证书都是一年,现在只有3个月.使得自动部署证书是必须要弄的事了.

 

 

httpsok

 

这是一个免费部署证书的,可以自动部署到CDN还有其他地方.

but,它免费用户配额有限, 只能免费3个证书,50次刷新CDN证书配额.

一个证书3个月,也就是你最多免费用9个月.就没办法免费用了.

当然也可以购买它高级版

https://httpsok.com/console/vip

25一年,但是只够一个通配符域名,普通用户也够了.不过我域名比较多.光刷新CDN证书次数就不够用.

 

 

自己部署

 

我自己服务器用的Caddy,它本身就有自动的证书请求.

在不到30天的时候,会自动更新证书,不需要任何外部介入.一切都是无缝处理.

 

然后我看看有没有上传证书到阿里云CDN的,经过GITHUB上找了下,发现一个比较简单好使的.

https://github.com/git9527/aliyun-cdn-https-cert-updater

克隆到服务器任意文件夹即可.

 

启用阿里云子账号

如果主账号调用API,权限很高,不安全.

在阿里云后台,RAM 访问控制,申请一个子账号,并申请access Key

存下来.

 

设置子账号权限

 

AliyunCDNFullAccess 管理CDN的权限
AliyunPCDNFullAccess 管理PCDN的权限
AliyunSCDNFullAccess 管理安全加速(SCDN)的权限
AliyunDCDNFullAccess 管理全站加速(DCDN)的权限
AliyunYundunCertFullAccess 管理云盾证书服务的权限

自定义一个权限组,添加权限
cas:ListTagResources   这个是管理资源,不开这个无法选择已经上传证书,AliyunYundunCertFullAccess不包含这个,所以这个一定要选.

 

 

测试脚本

 

上面的脚本是python的,我服务器有2.7版本测试可以用,理论上3.X也应该可以.

 


上传新的key文件和cert文件

python updater.py -i LTAI**** -s aBPGz3**** -d vshop.getce.cn -p ./certs/*.key -c ./certs/fullchain.cer

 

执行成功会返回

{"RequestId":"B764E9E0-77C8-5F73-BFE0-32C79ABC6C47"}

失败的话 有code字段.

 

上传后,不是自动部署的证书的,还要自己部署.

 

注意上面脚本中我指定的一个域名vshop.getce.cn这是我其中一个CDN域名.

上面脚本执行成功后,在阿里云盾证书,可以看到一个"vshop_年月日"的证书. 比如"vshop_20250106"

 

应用证书到域名

每次只能更新一个域名,-n后面的参数就是上一步的名字.

python updater.py -i LTAI**** -s aBPGz3**** -d vshop.getce.cn -n vshop_20250106
python updater.py -i LTAI**** -s aBPGz3**** -d www.getce.cn -n vshop_20250106

 

阿里云证书刷新很快,基本上更新后,刷新浏览器就看到证书变化了.

 

 

写一个自动更新脚本

 

上面测试了可行性,那么开始写脚本.

分几步.

第一步,判断CDN证书有效期.

第二步,判断本地证书有效期.

第三步,更新

 

判断CDN证书有效期

 

# 联网获取子域名的到期时间
echo | openssl s_client -servername vshop.getce.cn -connect vshop.getce.cn:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2

#输出类似Apr  8 12:33:45 2025 GMT

 

判断本地证书有效期

 

openssl x509 -enddate -noout -in 证书crt文件 | cut -d= -f2
#返回Apr  8 12:33:45 2025 GMT

 

更新

比较上面两个日期,然后上传证书.配置证书.

 

完整脚本

是同一个,文件,我分几段发.好理解.

下面几段拼成一个文件即可.

下面脚本大部分都是让百度AI写的,然后调整下就可以用了.

 

 

配置部分

 

# 指定证书根目录
ROOT_DIR="/var/lib/caddy/.local/share/caddy/certificates/"

# 自己的根域名
DOMAIN="getce.cn"

# 阿里云CDN要更新子域名数组
SUB_DOMAIN_ARRAY=("vshop" "www" "webapi" "login")


# 阿里云SECRET id和key
ALI_ACCESS_SECRET_ID=LT*******************
ALI_ACCESS_SECRET_KEY=sG*******************

# python可执行程序路径,我是2.7版本,可能3.x也可以用
PYTHON_BIN_PATH="/usr/bin/python"

# 阿里云证书更新脚本路径
ALI_SCRIPT_PATH="/data/aliyun-cdn-https-cert-updater/updater.py"



# ------------------- 上面是配置,下面代码一般不需要改动 --------------------------------



# 联网校验该子域名CDN的证书到期时间
TEST_SUB_HOST="${SUB_DOMAIN_ARRAY[0]}"  # 提取第一个子域名
TEST_PORT=443

# 拼接完整域名
FULL_TEST_HOST="${TEST_SUB_HOST}.${DOMAIN}"

# 上传后证书的名字
CERT_NAME="${TEST_SUB_HOST}_$(date +%Y%m%d)"

 

联网获取有效期

 

超过30天,不做任何动作,退出脚本.

 

# ------------------------- 开始联网读取域名的证书 --------------------


# 联网获取子域名的到期时间
TEST_HOST_CERT_DATE=$(echo | openssl s_client -servername ${FULL_TEST_HOST} -connect ${FULL_TEST_HOST}:${TEST_PORT} 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)

# 将到期日期转换为Unix时间戳(秒)
TEST_HOST_CERT_EXPIRY_TIMESTAMP=$(date -d "$TEST_HOST_CERT_DATE" +%s)

# 计算剩余天数
TEST_HOST_CERT_EXPIRY_DAY=$(( (TEST_HOST_CERT_EXPIRY_TIMESTAMP - $(date +%s)) / (3600 * 24) ))


# 根据剩余天数执行不同操作
if [ "$TEST_HOST_CERT_EXPIRY_DAY" -gt 30 ]; then
   echo "证书有效期还有 $TEST_HOST_CERT_EXPIRY_DAY 天,无需更新证书."
   exit 0
else
   echo "证书有效期只有 $TEST_HOST_CERT_EXPIRY_DAY 天,开始更新证书."
fi

 

获取本地证书

 

caddy颁发的证书是多个中间商的,每个中间商有不同文件夹.所以要搜索文件夹.找到wildcard通配符证书.

/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/
#acme-v02.api.letsencrypt.org-directory这个就是中间证书商文件夹,不同中间商可能文件夹不同.

搜到后,然后用openssl提取证书到期时间.

在所有枚举的证书中,找最新的一个.其他的不要.

 

# 用于存储最晚到期证书的变量
LATEST_CERT_FILE=""
LATEST_CERT_EXPIRY=""
LATEST_KEY_FILE=""
LATEST_CERT_EXPIRY_TIMESTAMP=0 # 初始化为0,或者一个足够小的值以确保第一个证书会被更新
LATEST_CERT_EXPIRY_DAY=0

# 使用for循环遍历find命令的输出,从指定目录的任何子目录里找到最新通配符证书(按到期时间)
# 之所以要枚举是因为caddy 自动生成letsencrypt证书可能有多个中间证书,放不同文件夹.
for CERT_FILE in $(find "$ROOT_DIR" -type f -name "wildcard_.${DOMAIN}.crt"); do
	# 提取目录和文件名(不带扩展名)
	CERT_DIR=$(dirname "$CERT_FILE")
	CERT_BASENAME=$(basename "$CERT_FILE" .crt)
	
	# 构造 .key 文件的路径
	KEY_FILE="$CERT_DIR/${CERT_BASENAME}.key"
	
	# 检查 .key 文件是否存在
	if [[ -f "$KEY_FILE" ]]; then
		# 使用 openssl 获取证书的到期时间
		EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$CERT_FILE" | cut -d= -f2)
		# 将到期时间转换为时间戳(秒)
		# 注意:这里假设EXPIRY_DATE的格式是"notAfter=YYYYMMDDHHMMSSZ",如果不是,需要调整date命令
		EXPIRY_TIMESTAMP=$(date -d "$EXPIRY_DATE" '+%s' 2>/dev/null || echo 0) # 添加错误处理,以防date命令失败
		
		EXPIRY_DAY=$(( (EXPIRY_TIMESTAMP - $(date +%s)) / (3600 * 24) ))
		
		# 如果时间戳转换失败(比如因为日期格式问题),则使用0作为时间戳,这样当前证书就不会被选为最新的
		if [[ "$EXPIRY_TIMESTAMP" -eq 0 ]]; then
			echo "Warning: Failed to parse expiry date from $CERT_FILE. Skipping this certificate." >&2
			continue # 跳过当前循环迭代
		fi
		
		# 如果这是我们找到的最晚到期的证书,则更新变量
		if [[ -z "$LATEST_CERT_EXPIRY" || "$EXPIRY_TIMESTAMP" -gt "$LATEST_CERT_EXPIRY_TIMESTAMP" ]]; then
			LATEST_CERT_FILE="$CERT_FILE"
			LATEST_CERT_EXPIRY="$EXPIRY_DATE"
			LATEST_CERT_EXPIRY_TIMESTAMP="$EXPIRY_TIMESTAMP"
			LATEST_CERT_EXPIRY_DAY="$EXPIRY_DAY"
			LATEST_KEY_FILE="$KEY_FILE"
		fi
	fi
done

 

 

比较到期时间

 

按到期时间,如果本地证书到期时间不到20天,不更新.

如果本地证书比服务器到期还早到期,也不更新.

# 输出最晚到期证书的 .crt 和 .key 文件路径以及到期时间
if [[ -n "$LATEST_CERT_FILE" ]]; then
	# echo "Latest Certificate File: $LATEST_CERT_FILE"
	# echo "Private Key File: $LATEST_KEY_FILE"
	# echo "Expiry Date: $LATEST_CERT_EXPIRY"
	# echo "Expiry Date(Timestamp): $LATEST_CERT_EXPIRY_TIMESTAMP"

	# 根据剩余天数执行不同操作
	if [ "$LATEST_CERT_EXPIRY_DAY" -lt 20 ]; then
	   echo "证书有效期只有 $LATEST_CERT_EXPIRY_DAY 天,小于20天,此证书没有更新必要."
	   exit 1
	else
	    # 本地证书和web证书到期日期比较
		if [ "$LATEST_CERT_EXPIRY_DAY" -lt "$TEST_HOST_CERT_EXPIRY_DAY" ]; then	
		   echo "本地证书比CDN证书更旧,此证书没有更新必要."
		   exit 1			
		else
		   echo "开始上传证书"		   
		fi
	fi	
else
	echo "没有找到 wildcard_.${DOMAIN}.crt"
	exit 1
fi

 

更新

 

阿里云API执行后有两种返回格式,

因为失败的有code字段,所以简单判断是否有code就可以判断执行状态.

 

# 开始上传证书
UPLOAD_CERT_INFO=$(${PYTHON_BIN_PATH} ${ALI_SCRIPT_PATH} -i ${ALI_ACCESS_SECRET_ID} -s ${ALI_ACCESS_SECRET_KEY} -d ${FULL_TEST_HOST} -p ${LATEST_KEY_FILE} -c ${LATEST_CERT_FILE} )


if echo "$UPLOAD_CERT_INFO" | grep -q '"code":'; then
   echo "上传本地证书错误,返回${UPLOAD_CERT_INFO}"
   exit 1
else
	# 更新每个子域名证书
	for item in "${SUB_DOMAIN_ARRAY[@]}"; do
		_name="${item}.${DOMAIN}"
		_update_sub_domain_info=$(${PYTHON_BIN_PATH} ${ALI_SCRIPT_PATH} -i ${ALI_ACCESS_SECRET_ID} -s ${ALI_ACCESS_SECRET_KEY} -d ${_name} -n ${CERT_NAME}  )
		if echo "$_update_sub_domain_info" | grep -q '"code":'; then
			echo "更新${_name}证书失败,返回${_update_sub_domain_info}"
		else
		    echo "更新${_name}证书成功!"
		fi
	done
fi

 

脚本执行测试

这里列举我两个域名,实际上所有域名都更新了.

 

加入计划任务

每天 0点0分执行.

0 0 * * * /root/get_new_crt.sh