申请免费证书
下面仍使用交互式 Bash 脚本,选择 Cloudflare DNS 或 HTTP 验证。Cloudflare API Token 至少应具有对应域名的 DNS 编辑权限和所需的 Zone 读取权限,资源范围限制到目标域名。
#!/usr/bin/env bash
set -euo pipefail
umask 077
trap 'echo "[失败] 上一步未完成,证书申请或安装已停止。" >&2' ERR
echo "====== acme.sh 证书申请脚本 ======"
read -r -p "请输入域名(如 example.com): " DOMAIN
DOMAIN="${DOMAIN#\*.}"
DOMAIN="${DOMAIN%.}"
DOMAIN="${DOMAIN,,}"
if (( ${#DOMAIN} > 253 )) || [[ "$DOMAIN" != *.* || "$DOMAIN" == *..* ]]; then
echo "域名格式不正确。" >&2
exit 1
fi
IFS='.' read -r -a DOMAIN_LABELS <<< "$DOMAIN"
for label in "${DOMAIN_LABELS[@]}"; do
if [[ ! "$label" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; then
echo "域名标签必须为1至63个字符,且不能以连字符开头或结尾。" >&2
exit 1
fi
done
TLD="${DOMAIN##*.}"
if [[ ! "$TLD" =~ ^([a-z]{2,63}|xn--[a-z0-9-]{1,59})$ ]]; then
echo "请输入普通 ASCII 域名或转换后的 Punycode 域名。" >&2
exit 1
fi
read -r -p "是否同时申请通配符 *.${DOMAIN}?[y/N]: " WILDCARD
read -r -p "请输入邮箱地址: " EMAIL
[[ -n "$EMAIL" ]] || { echo "邮箱不能为空。" >&2; exit 1; }
echo "请选择 CA:"
select CA_PROVIDER in "Let's Encrypt" "ZeroSSL"; do
case "$REPLY" in
1) CA_SERVER="letsencrypt"; break ;;
2) CA_SERVER="zerossl"; break ;;
*) echo "请输入 1 或 2" ;;
esac
done
echo "请选择验证方式:"
select VERIFY_METHOD in "Cloudflare DNS" "Standalone HTTP(80端口)"; do
case "$REPLY" in
1) DNS_MODE=true; break ;;
2) DNS_MODE=false; break ;;
*) echo "请输入 1 或 2" ;;
esac
done
if [[ "$WILDCARD" =~ ^[Yy]$ && "$DNS_MODE" != true ]]; then
echo "通配符证书需要 DNS-01 验证,请选择 Cloudflare DNS。" >&2
exit 1
fi
ACME_BIN="$HOME/.acme.sh/acme.sh"
if [[ ! -x "$ACME_BIN" ]]; then
curl -fsSL https://get.acme.sh | sh -s email="$EMAIL"
fi
[[ -x "$ACME_BIN" ]] || { echo "没有找到 acme.sh。" >&2; exit 1; }
"$ACME_BIN" --register-account --server "$CA_SERVER" -m "$EMAIL"
DOMAINS=(-d "$DOMAIN")
if [[ "$WILDCARD" =~ ^[Yy]$ ]]; then
DOMAINS+=(-d "*.$DOMAIN")
fi
ISSUE_ARGS=(--issue --server "$CA_SERVER" --keylength ec-256 "${DOMAINS[@]}")
if [[ "$DNS_MODE" == true ]]; then
read -r -s -p "请输入 Cloudflare API Token: " CF_API_TOKEN
echo
[[ -n "$CF_API_TOKEN" ]] || { echo "Token 不能为空。" >&2; exit 1; }
export CF_Token="$CF_API_TOKEN"
ISSUE_ARGS+=(--dns dns_cf)
else
echo "请先确认本机80端口空闲、域名解析到本机,并且公网可访问80端口。"
read -r -p "核对后按回车继续,或 Ctrl+C 退出。" _answer
command -v socat >/dev/null || { echo "Standalone 模式需要先安装 socat。" >&2; exit 1; }
ISSUE_ARGS+=(--standalone)
fi
# acme.sh 返回2表示跳过本次签发,已有证书可能仍可安装。
if "$ACME_BIN" "${ISSUE_ARGS[@]}"; then
echo "证书签发完成,开始安装。"
else
issue_status=$?
if [[ "$issue_status" -eq 2 ]]; then
echo "本次签发被跳过,继续安装已有证书;安装失败仍会停止。"
else
echo "证书签发失败,返回码: $issue_status" >&2
exit "$issue_status"
fi
fi
# 按完整域名区分目录,不能使用 ${DOMAIN#*.} 去掉第一级标签
CERT_DIR="/etc/ssl/$DOMAIN"
mkdir -p "$CERT_DIR"
read -r -p "证书更新后的服务重载命令(如 systemctl reload nginx;暂不配置可留空): " RELOAD_CMD
INSTALL_ARGS=(--install-cert -d "$DOMAIN" --ecc
--key-file "$CERT_DIR/key.pem"
--fullchain-file "$CERT_DIR/fullchain.pem")
if [[ -n "$RELOAD_CMD" ]]; then
INSTALL_ARGS+=(--reloadcmd "$RELOAD_CMD")
fi
"$ACME_BIN" "${INSTALL_ARGS[@]}"
echo "[成功] 证书已安装;本次可能为新签发,也可能复用已有证书。"
echo "私钥路径: $CERT_DIR/key.pem"
echo "完整证书路径: $CERT_DIR/fullchain.pem"
if [[ -z "$RELOAD_CMD" ]]; then
echo "尚未配置服务重载,启用证书时请手动检查并重载对应服务。"
fi
echo "请检查 acme.sh 的 cron/定时任务,确认续期任务实际存在。"
此脚本用于 Linux 的 Bash 环境,需预先安装 curl、openssl;Standalone 模式还需要 socat。以下使用管理员账号安装到 /etc/ssl,并以同一账号维护 acme.sh 的续期任务。
保存为 acme_cert_auto.sh,审阅后执行:
chmod +x acme_cert_auto.sh
sudo ./acme_cert_auto.sh
脚本会同时为主域名和可选的通配符签发 ECC 证书,按完整域名安装到单独目录。ZeroSSL 等 CA 的账号注册要求按当前服务提示处理;不要在错误后继续安装或把失败显示成成功。服务使用新证书还需要正确引用上述路径,并在续期后执行实际的重载命令。
评论将在接近此处时加载,也可点击立即加载。