使用acme申请并自动更新SSL证书
安装acme.sh(非root用户下执行)
# 运行安装脚本
wget -O - https://get.acme.sh | sh
# 让 acme.sh 命令生效
. .bashrc
# 开启 acme.sh 的自动升级
acme.sh --upgrade --auto-upgrade
证书申请-独立配置专属验证目录(非root用户下执行)
- Nginx 独立配置专属验证目录(非root用户下执行)
先创建专属验证目录,与实际网站路径解耦;该目录用于acme.sh通用验证,无必须放置index.html;
后续所有网站均可共用这一个目录;
# 创建目录
mkdir -p ~/websites/acme_challenge
# 创建index.html网页
nano ~/websites/acme_challenge/index.html
# 保存完index.html后
chmod -R a+r ~/websites/acme_challenge
简单网页 index.html
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>简单的网页</title> </head> <body> <h1>Hi!欢迎来到我的网页</h1> <p>这是一个非常简单的HTML网页。</p> </body> </html>
Nginx里配置80端口验证路径,sudo nano /etc/nginx/nginx.conf
#
# 统一处理所有常规网站的80端口
# - 浏览器访问指定长路径验证连通性
# - acme.sh 证书申请/更新的验证
# - https 重定向
#
server {
listen 80;
server_name a.xxx.com
b.xxx.com
c.xxx.com;
# 指定长路径,用于起初浏览器访问验证连通性
# alias后的路径下放index.html文件
location = /hello-world-test {
alias /home/username/websites/acme_challenge/;
try_files /index.html =404; # 必须是try_files,不是index, index会有重定向问题
}
# 所有域名的 acme 验证请求都会被拦截到这里, 与实际网站路径解耦
location /.well-known/acme-challenge/ {
root /home/username/websites/acme_challenge/; # 必须是root,不能是 alias
try_files $uri =404;
}
# 所有其余常规 80 端口访问,一律强制重定向到 HTTPS
location / {
# 重定向http->https
return 301 https://$host$request_uri;
}
}
- 申请(非root用户下执行)
将证书申请下载下来(但下载的证书存放的目录不是最终我们想要的目录,需要后续安装);
acme.sh --set-default-ca --server letsencrypt
# -w 后的目录是网站文件所在目录
# 与nginx中配置的80端口location /.well-known/acme-challenge/下的alias路径一致
acme.sh --issue -d xxxx.com -w ~/websites/acme_challenge --keylength ec-256 --force
# 申请成功
[Tue Jun 16 08:15:43 AM UTC 2026] Your cert is in: /home/username/.acme.sh/xxxx.com_ecc/xxxx.com.cer
[Tue Jun 16 08:15:43 AM UTC 2026] Your cert key is in: /home/username/.acme.sh/xxxx.com_ecc/xxxx.com.key
[Tue Jun 16 08:15:43 AM UTC 2026] The intermediate CA cert is in: /home/username/.acme.sh/xxxx.com_ecc/ca.cer
[Tue Jun 16 08:15:43 AM UTC 2026] And the full-chain cert is in: /home/username/.acme.sh/xxxx.com_ecc/fullchain.cer
[Tue Jun 16 08:15:44 AM UTC 2026] ARI suggestedWindow: 2026-08-14T10:01:15Z to 2026-08-16T05:12:05Z
[Tue Jun 16 08:15:44 AM UTC 2026] Next renewal time picked from ARI window: 2026-08-16T01:06:59Z
证书安装(非root用户下执行)
将证书安装/拷贝到指定目录
手动安装(不推荐,推荐后面的脚本方式安装):
acme.sh --install-cert -d xxx.com --cert-file ~/certs/xxx.com/cert.pem --key-file ~/certs/xxx.com/key.pem --fullchain-file ~/certs/xxx.com/fullchain_cert.pem --ecc # 安装成功 [Tue Feb 10 06:29:28 AM UTC 2026] Installing cert to: /home/username/certs/xxx.com/cert.pem [Tue Feb 10 06:29:28 AM UTC 2026] Installing key to: /home/username/certs/xxx.com/key.pem [Tue Feb 10 06:29:28 AM UTC 2026] Installing full chain to: /home/username/certs/xxx.com/fullchain_cert.pem # key.pem 文件默认对其他用户不可读,所以需要赋予其可读性权限 chmod +r ~/certs/xxx.com/key.pem chmod +r ~/certs/xxx.com/ -R脚本方式安装:
先创建certs目录,使用自动更新脚本安装,方便后续crontab自动化
# 创建certs目录
mkdir -p ~/certs/xxx.com
# 自动更新脚本
nano ~/certs/xxx.com/cert-update.sh
chmod +x ~/certs/xxx.com/cert-update.sh
cert-update.sh:
#!/bin/bash
# ==================== 配置变量 ====================
# 你的域名
DOMAIN="xxx.com"
# 证书存放的根目录
CERT_DIR="/home/username/certs/xxx.com"
# ==================================================
# 安装证书
/home/username/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--cert-file "${CERT_DIR}/cert.pem" \
--key-file "${CERT_DIR}/key.pem" \
--fullchain-file "${CERT_DIR}/fullchain_cert.pem" \
--ecc
echo "SSL Certificates Renewed"
echo | openssl s_client -connect "$DOMAIN":443 2>/dev/null | openssl x509 -noout -dates
# 修改权限
chmod +r "${CERT_DIR}/key.pem"
chmod +r "${CERT_DIR}/" -R
echo "Read Permission Granted for Private Key"
# 重载 Nginx
sudo systemctl reload nginx
echo "Nginx Reloaded"
然后该用户下直接执行./cert-update.sh
./certs/xxx.com/cert-update.sh
[Tue Jun 16 08:27:13 AM UTC 2026] Installing cert to: /home/username/certs/xxx.com/cert.pem
[Tue Jun 16 08:27:13 AM UTC 2026] Installing key to: /home/username/certs/xxx.com/key.pem
[Tue Jun 16 08:27:13 AM UTC 2026] Installing full chain to: /home/username/certs/xxx.com/fullchain_cert.pem
SSL Certificates Renewed
notBefore=Jun 11 10:27:51 2026 GMT
notAfter=Sep 9 10:27:50 2026 GMT
Read Permission Granted for Private Key
Nginx Reloaded
证书自动更新(非root用户下执行)
目前证书有效期时90天; acme.sh 通过在安装时自动添加定时任务(Cron Job),通常每天检查一次证书有效期。若证书临近过期(默认剩余少于30天),它会自动通过 DNS 或 HTTP 验证申请新证书并执行重载命令更新服务器,实现无需人工干预的自动续期。
但它并无法自动安装新证书,所以我们需要新增一个系统的自动周期任务来完成安装这一步。
!!!要编辑username用户下的,而非root用户下的!!!
# 要编辑username用户下的,而非root用户下的
crontab -e
crontab:
51 23 * * * "/home/username/.acme.sh"/acme.sh --cron --home "/home/username/.acme.sh" > /dev/null
# 1:00am, 1st day each month, run "cert-update.sh`
0 1 1 * * bash /home/username/certs/xxx.com/cert-update.sh
0 1 1 * * bash /home/username/certs/xxx.com/cert-update.sh
其他acme使用指南
Webroot 模式下路径变更
acme.sh初次申请证书是-w(Webroot 模式)传入的路径,如果路径后续不存在/变动,会影响后续证书的自动更新;
acme.sh 的 Webroot 模式工作原理如下:
- 申请/续期时,Let’s Encrypt 服务器会要求验证你对域名的所有权。
acme.sh会在你指定的本地文件夹(Webroot)下创建.well-known/acme-challenge/目录并写入一个随机验证文件。- Let’s Encrypt 会通过互联网访问
http://yourdomain.com/.well-known/acme-challenge/xxx。 - 问题所在:续期时是无人值守的自动化脚本在跑。如果路径
A变成了B,acme.sh依然把文件写到A(或者因为找不到A直接报错),而你的 Nginx/Web 服务器指向的是B,Let’s Encrypt 访问时就会返回 404 导致续期失败。
若路径变更,有两种方式修复:
方法一:直接修改
acme.sh的配置文件(推荐,最快)acme.sh为每个域名都维护了一个配置文件。找到该域名的配置目录(默认在
~/.acme.sh/你的域名/)打开
你的域名.conf文件找到
Le_Webroot这一行# 它看起来像这样: Le_Webroot='/old/path/to/folder' # 将其修改为新的绝对路径: Le_Webroot='/new/path/to/folder'修改后验证:
acme.sh --renew -d yourdomain.com --force
方法二:使用
--renew命令带上新路径强行刷新让
acme.sh带着新路径手动续期一次,它会自动覆盖旧的配置记录:acme.sh --renew -d yourdomain.com -w /new/path/to/folder --force
查看及管理历史域名
# 查看当前 acme.sh 还在维护的证书列表
acme.sh --list
# 从自动续期列表中移除
# --remove 命令后,acme.sh 只是把它从续期任务中剔除了,
# 但之前申请下来的证书文件、私钥以及域名的配置文件夹依然保存在服务器上(通常在 ~/.acme.sh/ 目录下)。
#为了彻底释放空间和保持清爽,可以手动把它的整套配置删掉:
acme.sh --remove -d olddomain.com
rm -rf ~/.acme.sh/olddomain.com/
-w路径错误导致失败的报错
Certbot ‘invalid response from well-known acme challenge’Certbot“来自知名 acme 质询的无效响应”
acme.sh --issue -d xxx.com -w /home/username/certs/xxx.com --keylength ec-256 --force
[Tue Jan 14 12:34:38 UTC 2025] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Tue Jan 14 12:34:38 UTC 2025] Single domain='xxx.com'
[Tue Jan 14 12:34:40 UTC 2025] Getting webroot for domain='xxx.com'
[Tue Jan 14 12:34:40 UTC 2025] Verifying: xxx.com
[Tue Jan 14 12:34:41 UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Tue Jan 14 12:34:45 UTC 2025] xxx.com: Invalid status. Verification error details: 47.79.32.103: Invalid response from http://xxx.com/.well-known/acme-challenge/hVhFFpEfCo4FYDW_tB567OoxeUKpfzfD3EAc8yOyoqg: 404
[Tue Jan 14 12:34:45 UTC 2025] Please add '--debug' or '--log' to see more information.
[Tue Jan 14 12:34:45 UTC 2025] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh