免费泛域名 SSL 证书申请指南
前言
在搭建网站时,HTTPS 已经成为标配。Let's Encrypt 等免费 CA 让证书申请变得简单,但对于泛域名证书(*.example.com),传统的 HTTP-01 验证方式无法使用,必须通过 DNS-01 验证。
本文详细记录在 OpenResty 环境下,使用 acme.sh 配合 DNSPod API 自动申请、部署、续期泛域名 SSL 证书的完整流程,包含踩坑记录和一键管理脚本,方便长久参考。
本文环境
| 项目 | 说明 |
|---|---|
| 操作系统 | Linux(Debian/Ubuntu/CentOS 通用) |
| Web 服务器 | OpenResty |
| 证书工具 | acme.sh v3.x |
| DNS 服务商 | DNSPod(腾讯云) (其他服务商如阿里云、华为云等也可以使用) |
| CA 机构 | ZeroSSL(国内访问比 Let's Encrypt 稳定) |
| 证书类型 | 泛域名证书(*.example.com + 根域) |
| 加密算法 | ECC P-256(ec-256,等效 RSA 3072) |
一、前置准备
1.1 域名准备
确保你的域名已在 DNSPod 托管解析,并且你有该域名的管理权限。
1.2 获取 DNSPod API 凭证
登录 DNSPod 控制台 → 右上角头像 → API 密钥 → DNSPod Token → 创建密钥,保存好 ID 和 Token。
注意:这是 DNSPod 自家的 Token,不是腾讯云 CAM 的 SecretId。域名在 DNSPod 管理用这个最简单。
1.3 准备一个真实邮箱
用于 ZeroSSL 账号绑定和证书到期提醒,必须是真实可接收邮件的邮箱,不能用 example.com 等占位域名。
二、安装 acme.sh
2.1 官方安装(指定邮箱)
curl https://get.acme.sh | sh -s email=your@email.com
source ~/.bashrc2.2 国内服务器安装(GitHub 不通时用 gitee 镜像)
git clone https://gitee.com/neilpang/acme.sh.git
cd acme.sh
./acme.sh --install -m your@email.com
source ~/.bashrc2.3 验证安装
/root/.acme.sh/acme.sh --version三、基础配置
3.1 设置默认 CA 为 ZeroSSL
/root/.acme.sh/acme.sh --set-default-ca --server zerosslZeroSSL 国内访问比 Let's Encrypt 稳定。其他可选:buypass、letsencrypt。
3.2 绑定 ZeroSSL 邮箱(强制要求)
/root/.acme.sh/acme.sh --register-account -m your@email.com --server zerosslZeroSSL 要求必须绑定邮箱才能注册账号,否则申请证书会报错。
3.3 国内网络优化(强制 HTTP/1.1)
国内服务器访问 CA 服务器经常遇到 curl error 35(TLS 握手失败)和 curl error 28(连接超时),强制使用 HTTP/1.1 可有效解决:
echo 'ACME_USE_HTTP_1=1' >> /root/.acme.sh/account.conf3.4 验证配置
grep -E 'DEFAULT_ACME_SERVER|ACCOUNT_EMAIL|ACME_USE_HTTP_1' /root/.acme.sh/account.conf应看到三项配置都已写入。
四、配置 DNSPod API 凭证
设置环境变量(只需首次设置,申请成功后 acme.sh 会自动保存到 account.conf):
export DP_Id="你的DNSPod_API_ID"
export DP_Key="你的DNSPod_API_Token"其他 DNS 服务商对照
| DNS 服务商 | --dns 参数 | 环境变量 |
|---|---|---|
| DNSPod(旧版 Token) | dns_dp | DP_Id、DP_Key |
| 腾讯云 CAM | dns_tencent | Tencent_SecretId、Tencent_SecretKey |
| 阿里云 | dns_ali | Ali_Key、Ali_Secret |
| Cloudflare | dns_cf | CF_Token |
五、申请泛域名证书(一步到位)
以下命令同时完成申请 + 直接部署到 OpenResty 证书目录,无需后续单独复制:
# 创建证书目录
mkdir -p /usr/local/openresty/nginx/conf/ssl
# 申请泛域名证书并直接输出到 OpenResty 目录
/root/.acme.sh/acme.sh --issue \
--dns dns_dp \
-d example.com \
-d '*.example.com' \
--keylength ec-256 \
--key-file /usr/local/openresty/nginx/conf/ssl/example.com.key \
--fullchain-file /usr/local/openresty/nginx/conf/ssl/example.com.crt \
--reloadcmd "/usr/local/openresty/nginx/sbin/nginx -s reload"参数详解
| 参数 | 说明 |
|---|---|
--dns dns_dp | 使用 DNSPod API 自动完成 DNS-01 验证 |
-d example.com | 根域名(必须同时申请,否则根域不认) |
-d '*.example.com' | 一级泛域名(单引号防止 shell 展开 *) |
--keylength ec-256 | ECC P-256 算法(性能好、体积小,等效 RSA 3072);用 RSA 则填 2048 |
--key-file | 私钥输出路径(部署到 Web 服务器目录) |
--fullchain-file | 完整证书链输出路径(服务器证书+中间证书,Nginx 必须用这个) |
--reloadcmd | 证书签发/续期成功后自动执行的重载命令 |
申请成功标志
日志最后出现 Cert success.,且目标目录下生成 .key 和 .crt 两个文件。
六、部署证书(单独部署方式)
如果申请时没有指定 --key-file/--fullchain-file,证书原件存在 /root/.acme.sh/example.com_ecc/,需手动部署:
/root/.acme.sh/acme.sh --install-cert -d example.com -d '*.example.com' --ecc \
--key-file /usr/local/openresty/nginx/conf/ssl/example.com.key \
--fullchain-file /usr/local/openresty/nginx/conf/ssl/example.com.crt \
--reloadcmd "/usr/local/openresty/nginx/sbin/nginx -s reload"ECC 证书所有操作加 --ecc;RSA 证书不加。七、配置 OpenResty 站点
7.1 完整 server 配置
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name example.com *.example.com;
return 301 https://$host$request_uri;
}
# HTTPS 站点
server {
listen 443 ssl http2;
server_name example.com *.example.com;
# 证书路径(和部署时一致)
ssl_certificate /usr/local/openresty/nginx/conf/ssl/example.com.crt;
ssl_certificate_key /usr/local/openresty/nginx/conf/ssl/example.com.key;
# SSL 安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HSTS(确认全站 HTTPS 后再启用)
# add_header Strict-Transport-Security "max-age=31536000" always;
# 站点根目录
root /data/wwwroot/example.com;
index index.html index.php;
# PHP 处理(oneinstack 默认)
location ~ \.php$ {
fastcgi_pass unix:/dev/shm/php-cgi.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
}7.2 测试并重载
/usr/local/openresty/nginx/sbin/nginx -t && /usr/local/openresty/nginx/sbin/nginx -s reload八、自动续期配置
8.1 acme.sh 自动安装 cron
安装时已自动写入 crontab,验证:
crontab -l | grep acme应看到类似:
17 16 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null8.2 如果 cron 丢失,手动安装
( crontab -l 2>/dev/null; echo '17 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null' ) | crontab -8.3 自动续期工作原理
每天定时执行 acme.sh --cron
├── 距到期 > 30 天 → 跳过
└── 距到期 ≤ 30 天 → 自动续签
├── 通过 DNS API 自动添加/删除 TXT 记录
├── 签发新证书
├── 自动覆盖部署目录的 .key/.crt
└── 自动执行 reloadcmd(重载 OpenResty)8.4 手动测试续期
# 强制续签指定域名(验证整条链路)
/root/.acme.sh/acme.sh --renew -d example.com --ecc --force
# 批量续签所有
/root/.acme.sh/acme.sh --cron --force九、一键管理脚本(完整代码)
将以下脚本保存为 /root/acme-manager.sh,封装申请、续签、检查、体检等常用操作:
#!/bin/bash
# =============================================================================
# acme-manager.sh - OpenResty 环境 acme.sh 证书一键管理脚本
# 功能: 申请(自动部署) / 手动续签 / 批量续签 / 列表 / 到期检查 / 日志 / cron管理 / 体检
# 适用: OpenResty + DNSPod + 通配符证书 + ZeroSSL CA
# =============================================================================
set -euo pipefail
# ============================ 配置区(按需修改) ============================
ACME_SH="/root/.acme.sh/acme.sh"
ACME_HOME="/root/.acme.sh"
NGINX_SSL_DIR="/usr/local/openresty/nginx/conf/ssl"
NGINX_RELOAD="/usr/local/openresty/nginx/sbin/nginx -s reload"
NGINX_TEST="/usr/local/openresty/nginx/sbin/nginx -t"
DNS_API="dns_dp" # DNSPod: dns_dp | 阿里云: dns_ali | 腾讯云CAM: dns_tencent
KEY_LENGTH="ec-256" # ec-256(ECC) 或 2048(RSA)
DEFAULT_CA="zerossl" # zerossl / letsencrypt / buypass
ACME_EMAIL="your@email.com" # ZeroSSL 账号邮箱,必填
LOG_FILE="/var/log/acme-manager.log"
DP_Id="" # DNSPod API ID(首次申请前填写)
DP_Key="" # DNSPod API Token
# =============================================================================
# 颜色输出
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; }
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE" 2>/dev/null || true; }
# 初始化检查
init_check() {
if [ ! -f "$ACME_SH" ]; then
error "未找到 acme.sh: $ACME_SH"
exit 1
fi
touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/acme-manager.log"
# 设置默认 CA
if ! grep -q "DEFAULT_ACME_SERVER" "$ACME_HOME/account.conf" 2>/dev/null; then
"$ACME_SH" --set-default-ca --server "$DEFAULT_CA" >/dev/null 2>&1
ok "已设置默认 CA: $DEFAULT_CA"
fi
# 国内网络修复
if ! grep -q "ACME_USE_HTTP_1=1" "$ACME_HOME/account.conf" 2>/dev/null; then
echo 'ACME_USE_HTTP_1=1' >> "$ACME_HOME/account.conf"
ok "已启用 ACME_USE_HTTP_1=1"
fi
# 绑定邮箱
if [ -n "$ACME_EMAIL" ] && [ "$ACME_EMAIL" != "your@email.com" ]; then
local current_email=""
current_email=$(grep -oP "ACCOUNT_EMAIL='\K[^']+" "$ACME_HOME/account.conf" 2>/dev/null || echo "")
if [ "$current_email" != "$ACME_EMAIL" ]; then
info "绑定/更新 ZeroSSL 邮箱: $ACME_EMAIL"
set +e
"$ACME_SH" --register-account -m "$ACME_EMAIL" --server zerossl >/dev/null 2>&1
set -e
if ! grep -q "ACCOUNT_EMAIL=" "$ACME_HOME/account.conf" 2>/dev/null; then
echo "ACCOUNT_EMAIL='$ACME_EMAIL'" >> "$ACME_HOME/account.conf"
fi
ok "邮箱绑定完成"
fi
fi
}
# 判断证书类型
get_cert_type() {
local domain="$1"
if [ -d "${ACME_HOME}/${domain}_ecc" ]; then echo "ecc"
elif [ -d "${ACME_HOME}/${domain}" ]; then echo "rsa"
else echo "none"; fi
}
get_ecc_flag() { [ "$1" = "ecc" ] && echo "--ecc" || echo ""; }
# 加载 DNSPod 凭证
load_dns_credentials() {
if grep -q "DP_Id=" "$ACME_HOME/account.conf" 2>/dev/null; then return 0; fi
if [ -n "$DP_Id" ] && [ -n "$DP_Key" ]; then
export DP_Id="$DP_Id" DP_Key="$DP_Key"
ok "已加载 DNSPod 凭证"
else
error "未配置 DNSPod 凭证,请在脚本配置区填写 DP_Id 和 DP_Key"
exit 1
fi
}
# 申请证书
cmd_issue() {
local domain="${1:-}"
[ -z "$domain" ] && { error "用法: $0 issue <域名>"; exit 1; }
init_check; load_dns_credentials
mkdir -p "$NGINX_SSL_DIR"
info "申请通配符证书: $domain + *.$domain"
set +e
"$ACME_SH" --issue --dns "$DNS_API" -d "$domain" -d "*.${domain}" \
--keylength "$KEY_LENGTH" \
--key-file "${NGINX_SSL_DIR}/${domain}.key" \
--fullchain-file "${NGINX_SSL_DIR}/${domain}.crt" \
--reloadcmd "$NGINX_RELOAD"
local ret=$?; set -e
if [ $ret -eq 0 ]; then
ok "证书申请成功!"
ok "证书: ${NGINX_SSL_DIR}/${domain}.crt"
log "ISSUE SUCCESS: $domain"
openssl x509 -in "${NGINX_SSL_DIR}/${domain}.crt" -noout -dates -subject 2>/dev/null || true
else
error "申请失败,错误码: $ret"
log "ISSUE FAILED: $domain (code=$ret)"
exit $ret
fi
}
# 强制续签
cmd_renew() {
local domain="${1:-}"
[ -z "$domain" ] && { error "用法: $0 renew <域名>"; exit 1; }
init_check
local cert_type=$(get_cert_type "$domain")
[ "$cert_type" = "none" ] && { error "未找到证书: $domain"; exit 1; }
local ecc_flag=$(get_ecc_flag "$cert_type")
info "强制续签: $domain ($cert_type)"
log "RENEW START: $domain"
set +e
"$ACME_SH" --renew -d "$domain" $ecc_flag --force
local ret=$?; set -e
if [ $ret -eq 0 ]; then
ok "续签成功: $domain"
log "RENEW SUCCESS: $domain"
local nginx_cert="${NGINX_SSL_DIR}/${domain}.crt"
[ -f "$nginx_cert" ] && openssl x509 -in "$nginx_cert" -noout -dates 2>/dev/null || true
else
error "续签失败: $domain (code=$ret)"
exit $ret
fi
}
# 批量续签
cmd_renew_all() {
init_check
info "强制续签所有证书..."
set +e; "$ACME_SH" --cron --force; local ret=$?; set -e
[ $ret -eq 0 ] && ok "批量续签完成" || warn "部分失败,查看: $0 log"
echo ""; "$ACME_SH" --list
}
# 列表
cmd_list() {
init_check
info "已管理的证书:"; echo ""
"$ACME_SH" --list
echo ""; info "OpenResty 部署目录:"
ls -lh "$NGINX_SSL_DIR"/ 2>/dev/null || warn "目录不存在"
}
# 到期检查
cmd_check() {
init_check
info "检查证书到期状态..."; echo ""
local now_ts=$(date +%s) warn_days=15 found=0
for cert_dir in "$ACME_HOME"/*/; do
[ -d "$cert_dir" ] || continue
local domain=$(basename "$cert_dir") cert_file=""
if [ -f "${cert_dir}${domain}.cer" ]; then cert_file="${cert_dir}${domain}.cer"
elif [ -f "${cert_dir}fullchain.cer" ]; then cert_file="${cert_dir}fullchain.cer"
else continue; fi
found=1
local expiry=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2)
[ -z "$expiry" ] && { warn "$domain: 无法读取到期时间"; continue; }
local expiry_ts=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
[ "$expiry_ts" = "0" ] && { warn "$domain: 解析失败 ($expiry)"; continue; }
local days_left=$(( (expiry_ts - now_ts) / 86400 ))
if [ "$days_left" -le 0 ]; then error "$domain: 已过期 ($expiry)"
elif [ "$days_left" -le "$warn_days" ]; then warn "$domain: ${days_left} 天后过期 【建议续签】"
else ok "$domain: ${days_left} 天后过期"; fi
# 部署一致性检查
local clean_domain="${domain%_ecc}"
local nginx_cert="${NGINX_SSL_DIR}/${clean_domain}.crt"
if [ -f "$nginx_cert" ]; then
local nginx_expiry=$(openssl x509 -in "$nginx_cert" -noout -enddate 2>/dev/null | cut -d= -f2)
[ "$nginx_expiry" != "$expiry" ] && warn " -> 部署证书与内部证书不一致,执行: $0 renew $clean_domain"
else
warn " -> 未找到部署证书: $nginx_cert"
fi
done
[ "$found" -eq 0 ] && warn "未找到任何证书"
echo ""; info "cron 任务:"
crontab -l 2>/dev/null | grep -q "acme.sh.*--cron" && { ok "已配置"; crontab -l | grep "acme.sh.*--cron"; } || error "未配置,执行: $0 cron-install"
}
# 日志
cmd_log() {
local lines="${1:-50}"
[ -f "$LOG_FILE" ] && { info "管理脚本日志:"; tail -n "$lines" "$LOG_FILE"; } || warn "无日志文件"
echo ""; info "acme.sh 日志:"
[ -f "${ACME_HOME}/acme.sh.log" ] && tail -n 20 "${ACME_HOME}/acme.sh.log" || warn "无日志"
}
# 安装 cron
cmd_cron_install() {
init_check
local cron_cmd="17 0 * * * \"$ACME_SH\" --cron --home \"$ACME_HOME\" > /dev/null"
if crontab -l 2>/dev/null | grep -q "acme.sh.*--cron"; then
warn "已存在 cron 任务:"; crontab -l | grep "acme.sh.*--cron"
read -r -p "是否覆盖?[y/N]: " answer
[[ "$answer" =~ ^[Yy]$ ]] && { ( crontab -l 2>/dev/null | grep -v "acme.sh.*--cron"; echo "$cron_cmd" ) | crontab -; ok "已更新"; }
else
( crontab -l 2>/dev/null; echo "$cron_cmd" ) | crontab -
ok "cron 已安装"
fi
}
# 一键体检
cmd_status() {
init_check
echo "==================== acme.sh 环境体检 ===================="
echo ""; echo "【1】版本"; "$ACME_SH" --version
echo ""; echo "【2】默认 CA"
grep -q "DEFAULT_ACME_SERVER" "$ACME_HOME/account.conf" 2>/dev/null && grep "DEFAULT_ACME_SERVER" "$ACME_HOME/account.conf" || warn "未设置"
echo ""; echo "【3】网络修复"
grep -q "ACME_USE_HTTP_1=1" "$ACME_HOME/account.conf" 2>/dev/null && ok "已启用" || warn "未启用"
echo ""; echo "【4】邮箱"
grep -q "ACCOUNT_EMAIL=" "$ACME_HOME/account.conf" 2>/dev/null && grep "ACCOUNT_EMAIL=" "$ACME_HOME/account.conf" || warn "未绑定"
echo ""; echo "【5】DNSPod 凭证"
grep -q "DP_Id=" "$ACME_HOME/account.conf" 2>/dev/null && ok "已保存" || warn "未保存"
echo ""; echo "【6】cron"
crontab -l 2>/dev/null | grep -q "acme.sh.*--cron" && ok "已配置" || error "未配置"
echo ""; echo "【7】证书列表"; "$ACME_SH" --list
echo ""; echo "【8】部署目录"
if [ -d "$NGINX_SSL_DIR" ]; then
ls -lh "$NGINX_SSL_DIR"/; echo ""
for cert_file in "$NGINX_SSL_DIR"/*.crt; do
[ -f "$cert_file" ] || continue
local dname=$(basename "$cert_file" .crt)
local exp=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2)
[ -n "$exp" ] && echo " $dname: 到期 $exp" || echo " $dname: 【警告】无法读取"
done
else warn "目录不存在"; fi
echo ""; echo "【9】OpenResty 配置测试"; $NGINX_TEST 2>&1 || true
echo ""; echo "==========================================================="
}
# 帮助
usage() {
cat << EOF
用法: $0 <命令> [参数]
issue <域名> 申请通配符证书并自动部署
renew <域名> 强制续签指定域名
renew-all 强制续签所有证书
list 列出所有证书
check 检查到期状态和部署一致性
log [行数] 查看日志
cron-install 安装/修复 cron
status 一键环境体检
EOF
}
# 主入口
case "${1:-}" in
issue) shift; cmd_issue "$@" ;;
renew) shift; cmd_renew "$@" ;;
renew-all) cmd_renew_all ;;
list) cmd_list ;;
check) cmd_check ;;
log) shift; cmd_log "${1:-50}" ;;
cron-install) cmd_cron_install ;;
status) cmd_status ;;
-h|--help|help|"") usage ;;
*) error "未知命令: $1"; usage; exit 1 ;;
esac安装和使用
# 上传到服务器
chmod 700 /root/acme-manager.sh
# 修改配置区(邮箱、DNSPod 凭证)
vi /root/acme-manager.sh
# 一键体检
/root/acme-manager.sh status
# 申请证书
/root/acme-manager.sh issue example.com
# 设置别名(可选)
echo "alias acme-mgr='/root/acme-manager.sh'" >> /root/.bashrc && source /root/.bashrc十、常用管理命令速查
# 列出所有证书
/root/.acme.sh/acme.sh --list
# 查看某证书详情(ECC 加 --ecc)
/root/.acme.sh/acme.sh --info -d example.com --ecc
# 查看证书文件信息
openssl x509 -in /usr/local/openresty/nginx/conf/ssl/example.com.crt -noout -dates -subject -issuer
# 检查证书链是否完整(应 >= 2 段)
grep -c "BEGIN CERTIFICATE" /usr/local/openresty/nginx/conf/ssl/example.com.crt
# 手动续签
/root/.acme.sh/acme.sh --renew -d example.com --ecc --force
# 批量续签所有
/root/.acme.sh/acme.sh --cron --force
# 吊销证书
/root/.acme.sh/acme.sh --revoke -d example.com --ecc
# 删除本地记录
/root/.acme.sh/acme.sh --remove -d example.com --ecc
# 查看日志
tail -50 /root/.acme.sh/acme.sh.log
# 升级 acme.sh(国内用 gitee 镜像)
/root/.acme.sh/acme.sh --upgrade --git-repo https://gitee.com/neilpang/acme.sh.git十一、常见问题与踩坑记录
11.1 curl error 35 / 28(国内网络不通)
现象:申请过程中出现 error code: 35(TLS 握手失败)和 error code: 28(连接超时)。
原因:服务器到 CA 服务器(ZeroSSL/Let's Encrypt)网络不通畅。
解决:
# 1. 强制 HTTP/1.1
echo 'ACME_USE_HTTP_1=1' >> /root/.acme.sh/account.conf
# 2. 切换到国内更稳定的 CA
/root/.acme.sh/acme.sh --set-default-ca --server zerossl
# 3. 仍不行则走代理
export HTTPS_PROXY=http://127.0.0.1:789011.2 浏览器不信任证书(证书链不完整)
现象:浏览器提示 NET::ERR_CERT_AUTHORITY_INVALID。
原因:Nginx 配置中 ssl_certificate 指向了只有服务器证书的文件,缺少中间证书。
解决:必须用 --fullchain-file 部署完整证书链,验证:
grep -c "BEGIN CERTIFICATE" /usr/local/openresty/nginx/conf/ssl/example.com.crt
# 返回 2 或 3 为正常,返回 1 说明缺少中间证书11.3 半成品记录(申请失败但列表里有记录)
现象:--list 里有记录,但 Created/Renew 为空,目录下没有 .cer 文件。
原因:申请过程中失败,留下了目录和配置文件但没有证书。
解决:直接删除目录后重新申请:
rm -rf /root/.acme.sh/example.com_ecc # ECC 证书
rm -rf /root/.acme.sh/example.com # RSA 证书11.4 ECC 证书管理忘加 --ecc
现象:执行 --info/--renew/--revoke 时报"证书不存在"。
原因:申请时用了 --keylength ec-256,后续所有操作必须加 --ecc。
解决:所有命令加 --ecc:
/root/.acme.sh/acme.sh --info -d example.com --ecc
/root/.acme.sh/acme.sh --renew -d example.com --ecc --force11.5 ZeroSSL 邮箱未绑定
现象:申请时报 Please update your account with an email address first。
解决:
/root/.acme.sh/acme.sh --register-account -m your@email.com --server zerossl11.6 误删除部署目录的证书
现象:不小心删了 /usr/local/openresty/nginx/conf/ssl/ 下的证书。
解决:不需要重新申请,从内部原件重新部署即可:
/root/.acme.sh/acme.sh --install-cert -d example.com -d '*.example.com' --ecc \
--key-file /usr/local/openresty/nginx/conf/ssl/example.com.key \
--fullchain-file /usr/local/openresty/nginx/conf/ssl/example.com.crt \
--reloadcmd "/usr/local/openresty/nginx/sbin/nginx -s reload"11.7 系统时间不对导致 TLS 握手失败
现象:curl error 35,但网络正常。
解决:
date # 检查时间
ntpdate ntp.aliyun.com # 同步时间十二、注意事项总结
- 泛域名只匹配一级:
*.example.com覆盖a.example.com,不覆盖a.b.example.com。 - 根域名必须同时申请:
-d example.com -d '*.example.com'缺一不可。 - 一张证书只能一个主域:不要把不同主域混在同一张泛域名证书里。
- 证书链必须完整:Nginx 必须用
fullchain文件,不能只用服务器证书。 - ECC 管理加 --ecc:申请用了 ec-256,后续所有操作都要加
--ecc。 - 国内网络优化:
ACME_USE_HTTP_1=1+ ZeroSSL CA,避免 curl 35/28。 - 凭证安全:DNSPod API Token 权限较大,脚本文件权限设为
700,不要泄露。 - 自动续期:acme.sh 自动安装 cron,到期前 30 天自动续签并 reload,无需人工干预。
- 原件 vs 部署副本:原件在
~/.acme.sh/,部署副本在 Nginx 目录,Nginx 引用部署副本。 - 频率限制:Let's Encrypt 每域名每周 50 张,测试用 staging 环境或 Buypass。
