我们将以通配符证书为例(使用DNS验证),并假定使用Cloudflare作为DNS服务商。进行自动安装SSL证书。
步骤:
安装acme.sh
配置Cloudflare API密钥(用于DNS验证)
申请通配符证书
配置证书自动部署到Nginx/Apache等服务器
设置自动续期
注意:通配符证书只能通过DNS验证方式申请。
假设我们的域名是example.com,需要申请*.example.com和example.com的证书。
使用acme.sh实现Let's Encrypt证书自动续期
acme.sh是轻量级的ACME协议客户端,专为自动化证书管理设计。以下是完整配置指南。
一、安装与基础配置
1. 一键安装
bash
方法1:官方一键安装(推荐)
curl https://get.acme.sh | sh -s email=my@example.com
方法2:指定安装目录(无root权限)
curl https://get.acme.sh | sh -s email=my@example.com --install \
--home ~/.acme.sh \
--config-home ~/.acme.sh/config \
--cert-home ~/.acme.sh/certs
方法3:从GitHub安装(中国大陆可用)
git clone https://github.com/acmesh-official/acme.sh.git
cd acme.sh
./acme.sh --install -m my@example.com
安装后加载环境变量
source ~/.bashrc 或 source ~/.zshrc
2. 配置默认参数
bash
查看当前配置
acme.sh --info
设置默认CA(推荐ZeroSSL,速度更快)
acme.sh --set-default-ca --server zerossl
或者使用Let's Encrypt(默认)
acme.sh --set-default-ca --server letsencrypt
配置证书存储路径
export CERT_HOME="/etc/ssl/acme"
mkdir -p $CERT_HOME
acme.sh --set-default-cert-home $CERT_HOME
配置账户邮箱(用于注册)
acme.sh --update-account --accountemail my@example.com
启用自动升级
acme.sh --upgrade --auto-upgrade
二、证书申请与验证
1. HTTP验证方式(适合单域名)
bash
创建webroot目录
mkdir -p /var/www/html/.well-known/acme-challenge
申请证书(webroot验证)
acme.sh --issue -d example.com -d www.example.com \
--webroot /var/www/html \
--keylength ec-256 # 使用ECC密钥(更安全)
或者使用standalone模式(自动启动临时web服务器)
acme.sh --issue -d example.com \
--standalone \
--listen-v4 \
--httpport 88 # 使用非标准端口
2. DNS验证方式(支持通配符)
bash
配置DNS API凭证(以Cloudflare为例)
export CF_Email="admin@example.com"
export CF_Key="your_cloudflare_api_key"
申请通配符证书
acme.sh --issue -d "*.example.com" -d example.com \
--dns dns_cf \
--keylength 2048 # RSA密钥(兼容性更好)
或者使用配置文件方式
cat > ~/.acme.sh/account.conf << EOF
SAVED_CF_Email='admin@example.com'
SAVED_CF_Key='your_cloudflare_api_key'
EOF
使用配置文件申请
acme.sh --issue -d "*.example.com" \
--dns dns_cf \
--dnssleep 120 # 等待DNS传播时间
3. 支持的DNS提供商列表
bash
查看支持的DNS提供商
acme.sh --list-dns
常用DNS提供商配置示例
1. Cloudflare
export CF_Token="your_api_token"
acme.sh --issue -d example.com --dns dns_cf
2.阿里云
export Ali_Key="LTAIxxxxxxxx"
export Ali_Secret="xxxxxxxx"
acme.sh --issue -d example.com --dns dns_ali
3. DNSPod
export DP_Id="12345"
export DP_Key="xxxxxxxx"
acme.sh --issue -d example.com --dns dns_dp
4.华为云
export HUAWEICLOUD_Username="username"
export HUAWEICLOUD_Password="password"
export HUAWEICLOUD_ProjectID="project_id"
acme.sh --issue -d example.com --dns dns_huaweicloud
5. 自定义脚本
acme.sh --issue -d example.com \
--dns dns_myapi \
--dnssleep 900
三、证书安装与部署
1. 安装到Nginx
bash
方法1:直接安装(acme.sh自动配置)
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com/key.pem \
--fullchain-file /etc/nginx/ssl/example.com/cert.pem \
--reloadcmd "systemctl reload nginx"
方法2:使用部署钩子
acme.sh --install-cert -d example.com \
--deploy-hook nginx
方法3:创建符号链接(推荐,便于管理)
acme.sh --install-cert -d example.com \
--cert-file /etc/ssl/acme/example.com/cert.pem \
--key-file /etc/ssl/acme/example.com/key.pem \
--fullchain-file /etc/ssl/acme/example.com/fullchain.pem \
--reloadcmd "ln -sf /etc/ssl/acme/example.com/*.pem /etc/nginx/ssl/ && systemctl reload nginx"
2. Nginx配置模板
nginx
nginx-acme.conf - 自动生成的配置模板
ssl_certificate /etc/ssl/acme/__DOMAIN__/fullchain.pem;
ssl_certificate_key /etc/ssl/acme/__DOMAIN__/key.pem;
ssl_trusted_certificate /etc/ssl/acme/__DOMAIN__/chain.pem;
OCSP装订配置
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
HSTS头
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
创建Nginx配置文件
cat > /etc/nginx/snippets/ssl-acme.conf << 'EOF'
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
EOF
3. Apache配置
bash
安装到Apache
acme.sh --install-cert -d example.com \
--cert-file /etc/ssl/acme/example.com/cert.pem \
--key-file /etc/ssl/acme/example.com/key.pem \
--fullchain-file /etc/ssl/acme/example.com/fullchain.pem \
--reloadcmd "systemctl reload apache2"
Apache SSL配置模板
cat > /etc/apache2/sites-available/example.com-ssl.conf << EOF
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/acme/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/acme/example.com/key.pem
启用HTTP/2
Protocols h2 http/1.1
HSTS头
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
DocumentRoot /var/www/html
</VirtualHost>
EOF
4. 多服务部署
bash
!/bin/bash
multi-service-deploy.sh
DOMAIN="example.com"
CERT_DIR="/etc/ssl/acme/$DOMAIN"
1. 申请证书
acme.sh --issue -d $DOMAIN -d www.$DOMAIN \
--dns dns_cf \
--keylength ec-256
2. 部署到Nginx
acme.sh --deploy -d $DOMAIN \
--deploy-hook nginx
3. 部署到Postfix(邮件服务器)
acme.sh --deploy -d $DOMAIN \
--deploy-hook postfix
4. 部署到Dovecot
acme.sh --deploy -d $DOMAIN \
--deploy-hook dovecot
5. 部署到HAProxy
acme.sh --deploy -d $DOMAIN \
--deploy-hook haproxy
6. 创建PEM格式(适合多种服务)
cat $CERT_DIR/fullchain.pem $CERT_DIR/key.pem > $CERT_DIR/combined.pem
chmod 600 $CERT_DIR/combined.pem
四、自动续期配置
1. 续期基础配置
bash
查看所有证书及其状态
acme.sh --list
手动续期单个证书
acme.sh --renew -d example.com --force
续期所有证书
acme.sh --renew-all --force
测试续期(不实际执行)
acme.sh --renew -d example.com --test
设置自动续期阈值(默认提前30天)
acme.sh --set-auto-renew --days 20 # 提前20天开始尝试续期
查看续期日志
tail -f ~/.acme.sh/acme.sh.log
2. Cron自动续期配置
bash
查看acme.sh的cron任务
crontab -l | grep acme.sh
手动安装cron任务(如果未自动安装)
acme.sh --install-cronjob
自定义cron计划(每天凌晨3点检查续期)
(crontab -l 2>/dev/null; echo "0 3 * * * \"$HOME/.acme.sh/acme.sh\" --cron --home \"$HOME/.acme.sh\" > /dev/null") | crontab -
或者使用systemd timer(更可靠)
cat > /etc/systemd/system/acme-renew.timer << EOF
[Unit]
Description=ACME Certificate Renewal Timer
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h
[Install]
WantedBy=timers.target
EOF
cat > /etc/systemd/system/acme-renew.service << EOF
[Unit]
Description=Renew ACME certificates
After=network.target
[Service]
Type=oneshot
User=root
Environment="HOME=/root"
ExecStart=/root/.acme.sh/acme.sh --cron --home /root/.acme.sh
StandardOutput=journal
StandardError=journal
EOF
systemctl enable --now acme-renew.timer
3. 续期通知配置
bash
配置邮件通知
acme.sh --set-notify --notify-hook mail \
--notify-level 2 \ # 1=错误, 2=续期成功, 3=所有事件
--notify-script "/path/to/notify.sh"
自定义通知脚本
cat > /usr/local/bin/acme-notify.sh << 'EOF'
!/bin/bash
acme.sh通知脚本
DOMAIN="$1"
STATUS="$2" # valid, invalid, renew_success, renew_failed
ACTION="$3" # issue, renew, delete
case $STATUS in
"renew_success")
发送邮件通知
echo "证书 $DOMAIN 续期成功" | mail -s "证书续期成功: $DOMAIN" admin@example.com
发送Slack通知
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"证书 $DOMAIN 续期成功\"}" \
https://hooks.slack.com/services/XXXX/XXXX/XXXX
;;
"renew_failed")
echo "证书 $DOMAIN 续期失败" | mail -s "紧急: 证书续期失败: $DOMAIN" admin@example.com
发送Telegram通知
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-d chat_id="$TELEGRAM_CHAT_ID" \
-d text="证书 $DOMAIN 续期失败,请立即检查!"
;;
esac
EOF
chmod +x /usr/local/bin/acme-notify.sh
应用通知配置
acme.sh --set-notify --notify-hook /usr/local/bin/acme-notify.sh
五、高级自动化场景
1. 多域名批量管理
bash
!/bin/bash
multi-domain-manager.sh
域名列表文件格式:每行一个域名,支持通配符
DOMAIN_LIST="/etc/acme/domains.list"
读取域名列表
while IFS= read -r domain || [[ -n "$domain" ]]; do
跳过空行和注释
[[ -z "$domain" || "$domain" =~ ^# ]] && continue
echo "处理域名: $domain"
检查证书是否已存在
if acme.sh --list | grep -q "$domain"; then
echo "证书已存在,检查续期"
acme.sh --renew -d "$domain" --force
else
echo "申请新证书"
判断是否通配符证书
if [[ "$domain" == *"*"* ]]; then
acme.sh --issue -d "$domain" --dns dns_cf
else
acme.sh --issue -d "$domain" --webroot /var/www/html
fi
安装证书
acme.sh --install-cert -d "$domain" \
--key-file "/etc/ssl/acme/$domain/key.pem" \
--fullchain-file "/etc/ssl/acme/$domain/fullchain.pem" \
--reloadcmd "/usr/local/bin/reload-services.sh $domain"
fi
done < "$DOMAIN_LIST"
2. Docker容器内使用
dockerfile
Dockerfile示例
FROM alpine:latest
安装acme.sh
RUN apk add --no-cache curl socat \
&& curl https://get.acme.sh | sh \
&& apk del curl
创建证书目录
RUN mkdir -p /acme/certs
复制配置脚本
COPY acme-config.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/acme-config.sh
设置入口点
ENTRYPOINT ["/usr/local/bin/acme-config.sh"]
bash
acme-config.sh - Docker容器内运行
!/bin/sh
设置DNS提供商凭证
export CF_Token="$CF_TOKEN"
申请证书
~/.acme.sh/acme.sh --issue -d "$DOMAIN" --dns dns_cf
导出证书
~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--key-file /acme/certs/key.pem \
--fullchain-file /acme/certs/fullchain.pem \
--reloadcmd "echo '证书已更新'"
保持容器运行
tail -f /dev/null
3. Kubernetes集成
yaml
acme-k8s-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: acme-cert-renewal
spec:
schedule: "0 2 * * *" # 每天凌晨2点
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: acme
image: neilpang/acme.sh
env:
- name: CF_Token
valueFrom:
secretKeyRef:
name: cloudflare-secret
key: api-token
- name: DOMAIN
value: "example.com"
command:
- /bin/sh
- -c
- |
申请或续期证书
acme.sh --issue -d $DOMAIN --dns dns_cf
导出证书到共享卷
acme.sh --install-cert -d $DOMAIN \
--key-file /certs/key.pem \
--fullchain-file /certs/fullchain.pem
更新Kubernetes Secret
kubectl create secret tls $DOMAIN-tls \
--cert=/certs/fullchain.pem \
--key=/certs/key.pem \
--dry-run=client -o yaml | \
kubectl apply -f -
volumeMounts:
- name: certs
mountPath: /certs
- name: kubeconfig
mountPath: /root/.kube
volumes:
- name: certs
emptyDir: {}
- name: kubeconfig
secret:
secretName: kubeconfig-secret
restartPolicy: OnFailure
六、故障排除与监控
1. 调试模式
bash
启用详细日志
acme.sh --issue -d example.com \
--debug \
--log-level 2 \
--log "/var/log/acme.log"
调试DNS验证
acme.sh --issue -d example.com \
--dns dns_cf \
--debug \
--dnssleep 300 \
--test #使用测试环境
检查证书详情
openssl x509 -in /etc/ssl/acme/example.com/fullchain.pem -text -noout
验证证书链
openssl verify -CAfile /etc/ssl/acme/example.com/chain.pem \
/etc/ssl/acme/example.com/fullchain.pem
2. 监控脚本
bash
!/bin/bash
cert-monitor.sh
CERT_DIR="/etc/ssl/acme"
ALERT_DAYS=7
NOW=$(date +%s)
check_cert_expiry() {
local cert_file="$1"
local domain=$(basename $(dirname "$cert_file"))
if [[ ! -f "$cert_file" ]]; then
echo "错误: 证书文件不存在: $cert_file"
return 1
fi
获取证书过期时间
local expiry_date=$(openssl x509 -in "$cert_file" -enddate -noout | cut -d= -f2)
local expiry_timestamp=$(date -d "$expiry_date" +%s)
local days_left=$(( (expiry_timestamp - NOW) / 86400 ))
if [[ $days_left -le $ALERT_DAYS ]]; then
echo " 警报: 证书 $domain 将在 $days_left 天后过期!"
发送警报
send_alert "$domain" "$days_left"
尝试自动续期
if [[ $days_left -lt 3 ]]; then
echo "尝试自动续期..."
acme.sh --renew -d "$domain" --force
fi
else
echo "正常: 证书 $domain 还有 $days_left 天过期"
fi
}
遍历所有证书
for cert_dir in $CERT_DIR/*/; do
cert_file="${cert_dir}fullchain.pem"
if [[ -f "$cert_file" ]]; then
check_cert_expiry "$cert_file"
fi
done
3. 常见问题解决
bash
问题1:证书续期失败,提示账户未注册
acme.sh --register-account -m my@example.com
问题2:DNS验证超时
增加等待时间
acme.sh --issue -d example.com --dns dns_cf --dnssleep 600
问题3:端口80被占用
使用备用端口或停止占用服务
acme.sh --issue -d example.com --standalone --httpport 88
问题4:证书链不完整
更新acme.sh并强制更新证书
acme.sh --upgrade
acme.sh --renew -d example.com --force
问题5:私钥权限问题
chmod 600 /etc/ssl/acme/example.com/key.pem
chown root:root /etc/ssl/acme/example.com/key.pem
七、安全最佳实践
1. 密钥安全
bash
使用ECC密钥(更安全,性能更好)
acme.sh --issue -d example.com \
--keylength ec-256 \
--ecc # 强制使用ECC
或使用更长的RSA密钥
acme.sh --issue -d example.com \
--keylength 4096
保护私钥文件
mkdir -p /etc/ssl/acme
chmod 700 /etc/ssl/acme
find /etc/ssl/acme -name "*.pem" -exec chmod 600 {} \;
2. 证书透明度日志
bash
启用证书透明度日志
acme.sh --issue -d example.com \
--preferred-chain "ISRG Root X1" \
--cert-file /etc/ssl/acme/example.com/cert.pem \
--ca-file /etc/ssl/acme/example.com/ca.pem \
--fullchain-file /etc/ssl/acme/example.com/fullchain.pem
验证证书是否在CT日志中
openssl x509 -in cert.pem -text | grep -A5 "CT Precertificate SCTs"
4. OCSP装订优化
bash
自动更新OCSP响应
cat > /usr/local/bin/update-ocsp.sh << 'EOF'
#!/bin/bash
DOMAIN="example.com"
CERT_DIR="/etc/ssl/acme/$DOMAIN"
获取OCSP响应
openssl ocsp -noverify -no_nonce \
-issuer "$CERT_DIR/chain.pem" \
-cert "$CERT_DIR/cert.pem" \
-url http://ocsp.int-x3.letsencrypt.org \
-respout "$CERT_DIR/ocsp.der"
转换为PEM格式(如果需要)
openssl ocsp -resp_in "$CERT_DIR/ocsp.der" \
-respout "$CERT_DIR/ocsp.pem" -noverify
EOF
添加到cron
echo "0 4 * * * /usr/local/bin/update-ocsp.sh" | crontab -
八、备份与恢复
1. 证书备份
bash
!/bin/bash
cert-backup.sh
BACKUP_DIR="/backup/acme/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
备份acme.sh配置
cp -r ~/.acme.sh $BACKUP_DIR/acme-home
备份证书文件
cp -r /etc/ssl/acme $BACKUP_DIR/certs
备份Nginx配置
cp -r /etc/nginx/ssl $BACKUP_DIR/nginx-ssl
创建压缩包
tar -czf /backup/acme-backup-$(date +%Y%m%d).tar.gz -C $BACKUP_DIR .
上传到云存储(可选)
rclone copy /backup/acme-backup-$(date +%Y%m%d).tar.gz backup:bucket/
echo "备份完成: $BACKUP_DIR"
2. 灾难恢复
bash
!/bin/bash
cert-recovery.sh
BACKUP_FILE="/backup/acme-backup-20240101.tar.gz"
RESTORE_DIR="/tmp/acme-restore"
解压备份
mkdir -p $RESTORE_DIR
tar -xzf $BACKUP_FILE -C $RESTORE_DIR
恢复acme.sh配置
cp -r $RESTORE_DIR/.acme.sh ~/
恢复证书文件
cp -r $RESTORE_DIR/certs/* /etc/ssl/acme/
恢复服务配置
cp -r $RESTORE_DIR/nginx-ssl/* /etc/nginx/ssl/
重新加载服务
systemctl reload nginx
systemctl reload postfix
验证恢复
for cert in /etc/ssl/acme/*/fullchain.pem; do
echo "验证证书: $(basename $(dirname $cert))"
openssl x509 -in $cert -noout -subject -dates
done
用户通过以上配置步骤,acme.sh可以实现完全自动化的Let's Encrypt证书管理,包括申请、部署、续期和监控,确保用户服务网站始终使用有效SSL证书。