跳到主要内容

申请免费证书


已读 0%
阅读设置
正文字号
18

设置仅保存在当前浏览器。

申请免费证书

下面仍使用交互式 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 的账号注册要求按当前服务提示处理;不要在错误后继续安装或把失败显示成成功。服务使用新证书还需要正确引用上述路径,并在续期后执行实际的重载命令。

参考:acme.sh 项目、Cloudflare DNS 验证接口。


文章作者: 0xdadream
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 0xdadream !
赏
评论

评论将在接近此处时加载,也可点击立即加载。