必要背景说明
  • 阿里云免费获取 ssl 证书是 20 个 3 个月的有效期,多个证书的创建以及获取的时间是不一致的,导致感觉很繁琐, acme.sh 可以解决这个问题

  • 可以选择证书的 CA 运营商, 默认是 ZeroSSL, 我根据网上的脚本修改为 Let’s Encrypt , 但后面我发现这个是存在限制的。它们的差异比较 参考信息; 关于使用 ZeroSSL 的配置参考这里

    image-20250712195200093

  • Let’s Encrypt 存在限制, 但实际个人使用基本不用关注它,可以简单的理解为 7 天 5 次证书。当然不止如此, 这里是具体的限制说明

    image-20250712195654544

具体实操
安装 acme.sh

项目本身存在中文基本使用说明, 按照说明文档中的存在以下方式:

curl https://get.acme.sh | sh -s email=my@example.com

wget -O -  https://get.acme.sh | sh -s email=my@example.com

然而事实上在国内很难访问, 并且在 https://get.acme.sh 中也存在 https://github.com/acmesh-official/acme.sh/wiki/Install-in-China 这个地址的引导到 https://gitee.com/neilpang/acme.sh, 那么就通过获取 git 库在进行安装的方式

git clone https://gitee.com/neilpang/acme.sh.git
cd acme.sh
./acme.sh --install -m my@example.com

image-20250712201304755

关于 alias 我使用的是 zsh, 它在 .zshrc 中添加了 . "/root/.acme.sh/acme.sh.env"

更换 CA 服务商(可选)
acme.sh --set-default-ca --server letsencrypt

关于这里, 文档说明了 ZeroSSL 一个可能出现的问题, 会一直出现 Pending,The CA is processing your order,please just wait., 而 Let's Encrypt 会出现的概率要小很多。

image-20250712202126206

参与文档地址 以及文中说明高级用法地址 (英文)

生成证书

我的需求仅是将阿里云的域名进行自动化获取, 没有兴趣对它进行深度研究,只关注它的自动验证并获取证书;也可以通过这个更详细的笔记博客进行查看手动方式

获取阿里云密钥并授权后

export Ali_Key="你的 key"
export Ali_Secret="你的 secret"
acme.sh --issue --dns dns_ali -d my@example.com

然后他就会去帮你在阿里云中的域名解析中临时创建 _acme-challenge.my 然后删除它,你会收到阿里云的站内信提醒你删除。

安装(复制)证书

image-20250724013625208

这里的输出的文件名和路径并没有严格的要求, 按照你实际管理的路径和文件名即可。例如使用 Nginx, 配合它的配置进行设置路径和文件名等,我用的是 Nginx。

acme.sh --install-cert -d my.example.com \
--key-file       /path/to/keyfile/in/nginx/my.example.com.key  \
--fullchain-file /path/to/fullchain/nginx/my.example.com.crt \
--reloadcmd     "service nginx force-reload"

image-20250712204306338

后续的其他说明

一开始我以为它的证书数量也是 20次/年, 证书的有效时长是 3 个月, 2 个月就会更新, 就疯狂的寻找,让重新获取证书的时间为 87 天, 然后发现 --days 以及 --valid-to --valid-from 等参数其中 --days 控制本地重新获取新证书的操作, --valid-to--valid-from 是对证书时效的操作。但是 Let’s Encrypt ZeroSSL 没有 20次/年 的限制, 那么就无所谓了。


扩展补充: 本地进行 acme.sh 管理, 远程进行操作

基于云服务器本质是托管服务, 核心数据保存本地的思路, 将 acme.sh 迁移到本地 7*24 内网服务器中;

image-20251127153835335

  1. 首先在外网可访问服务器中创建一个远程登录进行交互的用户和部署脚本

  2. 为这个用户专门配置一个 sudo 文件, 允许它免密提权操作部署脚本

  3. 将远程服务器的 acme.sh 迁移到本地服务器, 然后进行安装和调整

  4. 重新调整 acme.sh 域名的自动化配置

  5. 定义 function 便捷化将来可能存在的新部署

具体操作
  • 远端服务器

    • 先创建部署脚本

      sudo nvim {SCRIPT_PATH}/deploy-cert.sh
      ---
      #!/bin/bash
      
      cert_path={NGINX_CERT_PATH}
      temp_path={CERT_UPLOAD_PATH}
      
      echo "[$(date)] 开始部署证书..."
      
      # 检查临时目录是否有文件
      if [ ! "$(ls -A $temp_path)" ]; then
          echo "错误: 临时目录 $temp_path 为空"
          exit 1
      fi
      
      # 移动证书
      echo "移动证书文件到 $cert_path"
      sudo mv "$temp_path"/* "$cert_path" || {
          echo "错误: 移动证书失败"
          exit 1
      }
      
      # 修改权限
      echo "设置证书权限..."
      sudo chown root:root "$cert_path"/*.cer "$cert_path"/*.key || {
          echo "错误: 修改所有者失败"
          exit 1
      }
      
      sudo chmod 600 "$cert_path"/*.cer "$cert_path"/*.key || {
          echo "错误: 修改权限失败"
          exit 1
      }
      
      # 重启 Nginx
      echo "重新加载 Nginx..."
      sudo nginx -t || {
          echo "错误: Nginx 配置测试失败"
          exit 1
      }
      
      sudo nginx -s reload || {
          echo "错误: Nginx 重载失败"
          exit 1
      }
      
      echo "[$(date)] 证书部署成功!"
      exit 0
      ---
      sudo chmod 700 {SCRIPT_PATH}/deploy_cert.sh
      
    • 创建和配置专门处理用户

      sudo useradd -m -s /bin/bash {NEW_USER}
      
      sudo nvim /etc/sudoers.d/{NEW_USER}
      ---
      {NEW_USER} ALL=(ALL) NOPASSWD: {SCRIPT_PATH}/deploy-cert.sh
      ---
      
      sudo nvim /etc/ssh/sshd_config.d/{NEW_USER}.conf
      ---
      Match User cert-deploy
        PermitTTY no
        X11Forwarding no
        AllowTcpForwarding no
        PermitTunnel no
      ---
      sudo systemctl restart sshd
      
    • 打包 acme.sh 后边用于拉取到本地

      # 注意文件权限是否可以被 scp 获取
      tar -zcf ~/acme.tar.gz {ACME.SH_PATH}
      
  • 迁移 acme.sh

    scp {REMOTE_USER}@{REMOTE_IP}:~/acme.tar.gz .
    tar -zxf acme.tar.gz
    # 我并不喜欢将它放在 $HOME 下, 实际使用了软链接的方式进行了放置, 如果 acme.sh 出现了问题, grep -rn 进行查找报错信息进行修改, 一般是路径问题, 然后也仅在 $HOME/.acme.sh 这一级目录下, 别去子目录调整
    ./.acme.sh/acme.sh --install
    
  • 创建本地部署脚本

    • 脚本之前创建密钥, 用于 ssh

      • 本地

        # 一直回车别设置密码
        ssh-keygen -t ed25519 -f ~/.ssh/cert-deploy -C "cert-deploy-automation"
        
      • 远端

        sudo mkdir ~{REMOTE_USER}/.ssh
        sudo chown {REMOTE_USER}:{REMOTE_USER} ~{REMOTE_USER}/.ssh
        sudo chmod 700 ~{REMOTE_USER}/.ssh
        
        sudo nvim ~{REMOTE_USER}/.ssh/authorized_keys
        ---
        {这里是之前生成的公钥, 即本地的 ~/.ssh/cert-deploy.pub 的文本信息}
        ---
        sudo chown {REMOTE_USER}:{REMOTE_USER} ~{REMOTE_USER}/.ssh/authorized_keys
        sudo chmod 600 ~{REMOTE_USER}/.ssh/authorized_keys
        
    • acme.sh 部署执行脚本

      nvim {LOCAL_DEPLOY_PATH}/acme_deploy_cert.sh
      ---
      #!/bin/bash
      
      #==============================================================================
      # 证书自动部署脚本
      # 功能: 上传本地 acme.sh 证书到远端服务器,并执行部署脚本
      #==============================================================================
      
      # ====== 配置区域 - 请根据实际情况修改 ======
      
      # 远端服务器配置
      REMOTE_USER="{REMOTE_USER}"              # 远端服务器用户名
      REMOTE_HOST="{REMOTE_IP}"        # 远端服务器地址
      SSH_PORT="{REMOTE_PORT}"
      SSH_KEY="$HOME/.ssh/cert-deploy"          # SSH 私钥路径
      
      # 域名配置
      DOMAIN="$1"                 # 你的域名
      
      # 本地证书路径 (acme.sh 默认路径)
      # 这里的路径可能不同, 如果你 acme.sh --issue 没有使用 --keylength ec-256 之类的命令, 就没有 _ess
      CERT_DIR="$HOME/.acme.sh/${DOMAIN}_ecc"
      # CERT_DIR="$HOME/.acme.sh/${DOMAIN}"
      CERT_FILE="${CERT_DIR}/${DOMAIN}.cer"
      KEY_FILE="${CERT_DIR}/${DOMAIN}.key"
      
      # 远端路径
      REMOTE_TEMP_DIR="/home/${REMOTE_USER}/certs"     # 远端临时目录
      REMOTE_DEPLOY_SCRIPT="{SCRIPT_PATH}/deploy-cert.sh"  # 远端部署脚本路径
      
      # 日志配置 (可选)
      ENABLE_LOG=true                      # 是否启用日志: true 或 false
      LOG_FILE="$HOME/logs/cert-deploy.log"
      
      # ====== 以下为脚本逻辑,一般无需修改 ======
      
      # 日志函数
      log() {
          local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
          echo "$msg"
          if [ "$ENABLE_LOG" = true ]; then
              mkdir -p "$(dirname "$LOG_FILE")"
              echo "$msg" >> "$LOG_FILE"
          fi
      }
      
      # 错误处理函数
      error_exit() {
          log "错误: $1"
          exit 1
      }
      
      # 主流程
      main() {
          log "=========================================="
          log "开始部署证书到 ${REMOTE_HOST}"
          log "=========================================="
      
          # 检查本地证书文件是否存在
          if [ ! -f "$CERT_FILE" ]; then
              error_exit "证书文件不存在: $CERT_FILE"
          fi
      
          if [ ! -f "$KEY_FILE" ]; then
              error_exit "密钥文件不存在: $KEY_FILE"
          fi
      
          log "本地证书文件检查通过"
      
          # 创建远端临时目录
          #log "创建远端临时目录: ${REMOTE_TEMP_DIR}"
          #ssh -i "$SSH_KEY" "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p ${REMOTE_TEMP_DIR}" || \
          #    error_exit "创建远端目录失败"
      
          # 上传证书文件
          log "上传证书文件$CERT_FILE和$KEY_FILE到远端服务器..."
          scp -P "$SSH_PORT" -i "$SSH_KEY" \
              "$CERT_FILE" \
              "$KEY_FILE" \
              "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_TEMP_DIR}/" || \
              error_exit "证书上传失败"
      
          log "证书上传成功"
      
          # 执行远端部署脚本
          log "执行远端部署脚本: ${REMOTE_DEPLOY_SCRIPT}"
          ssh -p "$SSH_PORT" -i "$SSH_KEY" "${REMOTE_USER}@${REMOTE_HOST}" "sudo ${REMOTE_DEPLOY_SCRIPT}" || \
              error_exit "远端部署脚本执行失败"
      
          log "远端部署脚本执行成功"
          log "=========================================="
          log "证书部署完成!"
          log "=========================================="
      }
      
      # 执行主流程
      main
      ---
      chmod +x {LOCAL_DEPLOY_PATH}/acme_deploy_cert.sh
      
  • 调整 acme.sh 的自动续签

    # 几个域名执行几次
    acme.sh --install-cert -d {DOMAIN} --reloadcmd "{LOCAL_DEPLOY_PATH}/acme_deploy_cert.sh {DOMAIN}"
    
  • 在 .bashrc 添加便捷的 function

    # acme
    acme_new() {
      acme.sh --issue --dns dns_ali -d "$1" --keylength ec-256
    }
    
    acme_install_cert() {
      acme.sh --install-cert -d "$1" --reloadcmd "{LOCAL_DEPLOY_PATH}/acme_deploy_cert.sh $1"
    }