AutoSSL 教程:Go 实现的 ACME 自动证书管理
网站的 HTTPS 证书每 90 天过期一次,手动申请、上传、续期既繁琐又容易忘,一次疏忽就是全站证书过期告警。AutoSSL 就是为解决这个问题而生的工具:基于 Go 开发的 ACME 自动证书管理服务,支持 Let's Encrypt 与 ZeroSSL、HTTP-01 与 DNS-01 两种验证方式,带 Web 管理界面,证书到期前自动续期,还能在到期前通过飞书或邮件提醒你。本文从部署、配置、申请证书到自动续期与 API 调用,把 AutoSSL 的完整用法讲透。
一、项目简介
AutoSSL(仓库名 Auto-ssl)是 git.cnbugs.com 上 AI-Agent 组织的一个开源项目,用 Go 语言开发,前端是 Vue 3 + Vite,后端是 Go 内嵌静态资源的单体服务。它通过 ACME 协议向 Let's Encrypt 或 ZeroSSL 申请免费 SSL 证书,并把证书文件落盘到指定目录,供 Nginx、Apache、CDN 等直接使用。
和 certbot 这类命令行工具相比,AutoSSL 最大的不同是它是一个带 Web 界面的长期运行服务:证书列表、申请、续期、删除、批量续期、到期通知都在浏览器里完成,不用记一堆命令行参数;和宝塔这类面板相比,它只做证书这一件事,部署轻、依赖少,一个 Docker 容器就能跑。
- 仓库:git.cnbugs.com/AI-Agent/Auto-ssl
- 语言:Go(后端)+ Vue 3 + TypeScript(前端)
- 部署:Docker 一键部署,或编译后直接运行
- 证书来源:Let's Encrypt、ZeroSSL(支持 EAB)
- 数据存储:本地文件(./data),无外部数据库依赖
二、功能特性
| 功能 | 说明 |
|---|---|
| 自动申请证书 | 支持 Let's Encrypt 与 ZeroSSL,支持单域名与多域名(SAN) |
| 自动续期 | 每天凌晨 3 点自动检查并续期即将到期的证书 |
| HTTP-01 验证 | 通过 80 端口完成验证,需把域名解析到服务器 |
| DNS-01 验证 | 通过 DNS TXT 记录完成验证,支持阿里云、Cloudflare、DNSPod、腾讯云 DNS 的 API 自动解析 |
| DNS-01 手动解析 | 没有 DNS 服务商 API 密钥也能申请:提交后查看待添加的 TXT 记录,手动添加后点确认继续验证 |
| Web 管理界面 | 仪表盘、证书列表、申请、查看、续期、删除全在浏览器里完成 |
| 用户登录认证 | JWT Token 登录,管理员账号密码可配置 |
| 管理员改密 | Web 界面右上角菜单改密码,密码持久化存储 |
| 证书详情 | 查看 SAN、颁发者、有效期、序列号、签名算法 |
| 批量续期 | 证书列表多选,一键批量续期 |
| ZeroSSL EAB | 申请 ZeroSSL 证书时可填 EAB Key ID 与 HMAC Key |
| 到期通知 | 飞书机器人与邮件两种方式,30 天内到期的证书会触发通知 |
| Docker 一键部署 | docker compose up -d 即可运行 |
三、适用场景
- 个人博客与小站:一两个域名,用 HTTP-01 验证最省事,装好就不用管,证书到期自动续。
- 多域名、多子域名:一个证书里放多个 SAN,一次申请覆盖 www、api、cdn 等多个子域名。
- 内网或 80 端口不开放:用 DNS-01 验证,不需要服务器开放 80 端口,适合只有 443 或内网环境的机器。
- 没有 DNS API 密钥:用 DNS-01 手动解析模式,手动加一条 TXT 记录就能申请,适合域名在小服务商、拿不到 API 密钥的情况。
- 多台服务器共享证书:证书文件落盘在 ./data/certs/<domain>/,可以直接 rsync 或挂载分发到其他机器的 Nginx、CDN 上。
- 需要到期提醒:接飞书机器人或邮件,每天早上 8 点自动检查,30 天内到期的证书会发通知。
四、工作原理:ACME 协议与两种验证方式
AutoSSL 通过 ACME 协议(RFC 8555)向证书颁发机构申请证书,流程大致是:注册 ACME 账号 → 提交域名订单 → 按要求完成域名所有权验证 → 颁发机构签发证书 → 服务端保存证书文件并定时续期。域名所有权验证有两种方式,选哪种取决于你的网络与 DNS 条件。
| 对比项 | HTTP-01 | DNS-01 |
|---|---|---|
| 验证方式 | 在域名 80 端口放一个指定路径的文件,颁发机构来取 | 在域名 DNS 里加一条 _acme-challenge TXT 记录 |
| 端口要求 | 服务器 80 端口必须能从公网访问 | 不需要开放任何端口 |
| 通配符证书 | 不支持(*.example.com 必须用 DNS-01) | 支持 |
| DNS 要求 | 域名解析到这台服务器即可 | 需要 DNS 服务商 API 自动加记录,或手动添加 |
| 适用场景 | 域名直接解析到本机、有公网 80 端口 | 通配符证书、内网机器、80 端口不开放 |
| AutoSSL 的实现 | 内置 HTTP-01 挑战服务,默认监听 8082 端口(ACME_PORT) | 支持阿里云、Cloudflare、DNSPod、腾讯云 DNS 的 API;无 API 密钥时可用手动解析模式 |
需要注意:HTTP-01 验证时,颁发机构会访问 http://你的域名/.well-known/acme-challenge/<token>,所以域名必须解析到这台服务器,且 80 端口要通。如果你的 80 端口被 Nginx 占用,AutoSSL 的 HTTP-01 挑战服务默认跑在 8082 端口,需要在 Nginx 里把 /.well-known/acme-challenge/ 代理到 127.0.0.1:8082,具体配置见第十一节。
五、Docker 一键部署
推荐用 Docker 部署,一条命令启动,数据通过 volume 持久化到 ./data 目录。
# 克隆仓库
git clone https://git.cnbugs.com/AI-Agent/Auto-ssl.git autossl
cd autossl
# 启动(后台运行)
docker compose up -d
# 查看启动日志,首次启动会自动生成随机管理员密码
docker logs -f autossl
仓库自带的 docker-compose.yml 做了这些事:构建镜像、容器名 autossl、restart: unless-stopped、映射 8080(Web 界面与 API)与 8082(HTTP-01 挑战服务)两个端口、把 ./data 挂载到容器内的 /app/data、时区设为 Asia/Shanghai。关键环境变量在 compose 文件里可以直接改:
services:
autossl:
build: .
container_name: autossl
restart: unless-stopped
ports:
- "8080:8080" # Web UI and API
- "8082:8082" # HTTP-01 challenge server (ACME_PORT)
environment:
- PORT=8080
- TZ=Asia/Shanghai
- ADMIN_USER=admin
- ADMIN_PASSWORD=your-strong-password
- JWT_SECRET=your-random-secret
# - FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/xxxxx
volumes:
- ./data:/app/data
启动后访问 http://服务器IP:8080,用 admin 与你设置的密码登录。如果 ADMIN_PASSWORD 没设置,首次启动会自动生成随机密码并打印到日志里,用 docker logs autossl 查看,登录后在 Web 界面右上角菜单里改密码。JWT_SECRET 如果不设置,每次启动都会随机生成,重启后之前签发的 token 全部失效,生产环境务必设置一个固定值。
六、直接运行与 systemd 部署
不想用 Docker 也可以直接编译运行,需要 Go 环境。
# 编译(在 backend 目录)
cd backend && go build -o autossl .
# 运行(默认端口 8080)
./autossl
# 指定端口与数据目录运行
PORT=9090 DATA_DIR=/var/lib/autossl ./autossl
生产环境建议用 systemd 托管,仓库里自带 autossl.service 模板(其中的 {{DEPLOY_DIR}} 是占位符,部署脚本会替换成实际路径,手动部署时自己改成真实路径):
[Unit]
Description=Auto-SSL 证书管理服务
After=network.target
Wants=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/autossl/backend
Environment="PORT=9090"
Environment="ACME_PORT=8082"
Environment="GIN_MODE=release"
Environment="ADMIN_USER=admin"
Environment="ADMIN_PASSWORD=your-strong-password"
Environment="JWT_SECRET=your-random-secret"
ExecStart=/opt/autossl/backend/autossl
Restart=always
RestartSec=3
StandardOutput=journal+console
StandardError=journal+console
SyslogIdentifier=autossl
[Install]
WantedBy=multi-user.target
# 安装并启动服务
cp autossl.service /etc/systemd/system/autossl.service
systemctl daemon-reload
systemctl enable --now autossl
systemctl status autossl
# 查看日志
journalctl -u autossl -f
注意这个模板默认用 9090 端口(PORT=9090),和 Docker 部署的 8080 不一样,访问时按实际端口来;ACME_PORT 固定 8082,是 HTTP-01 挑战服务的端口。
七、配置详解:环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
| PORT | 8080 | Web 服务端口 |
| DATA_DIR | ./data | 数据存储目录 |
| CERT_DIR | ./data/certs | 证书文件存储目录 |
| ACCOUNTS_DIR | ./data/accounts | ACME 账号存储目录 |
| ACME_PORT | 8082 | HTTP-01 挑战端口 |
| ADMIN_USER | admin | 管理员用户名 |
| ADMIN_PASSWORD | 未设置时自动生成 | 管理员密码;未设置时首次启动自动生成随机密码并打印到日志,登录后可用 Web 界面「修改密码」更换 |
| JWT_SECRET | 未设置时每次启动随机生成 | JWT 签名密钥;生产环境建议设置固定值,否则重启后已签发的 token 失效 |
| CORS_ORIGINS | - | 允许跨域的源(逗号分隔,如 https://example.com);默认不开启 CORS |
| FEISHU_WEBHOOK_URL | - | 飞书机器人 Webhook 地址(用于证书到期通知) |
| SMTP_HOST | - | SMTP 服务器地址(用于邮件通知) |
| SMTP_PORT | - | SMTP 端口 |
| SMTP_USER | - | SMTP 用户名 |
| SMTP_PASSWORD | - | SMTP 密码 |
| SMTP_FROM | - | 发件人地址 |
| NOTIFY_TO | - | 收件人邮箱(多个用逗号分隔) |
生产环境最少要固定三个值:ADMIN_PASSWORD、JWT_SECRET,以及按需配置的通知变量。ADMIN_PASSWORD 不设也能跑,但随机密码只在启动日志里出现一次,日志一滚就找不到了;JWT_SECRET 不设会导致每次重启都要重新登录。建议用 .env 文件或 docker-compose 的 environment 集中管理,不要把密码写进镜像里。
八、申请证书实战
8.1 HTTP-01 方式(最常用)
- 域名 DNS 解析到这台服务器,确认 80 端口能从公网访问。
- 登录 Web 界面,点「申请证书」,填域名(如 www.example.com,可一次填多个域名做 SAN)。
- 验证方式选 HTTP-01,颁发机构选 Let's Encrypt,提交。
- AutoSSL 会在 8082 端口起挑战服务,颁发机构验证通过后签发证书,证书出现在列表里,状态变为有效。
如果 80 端口被 Nginx 占用,需要把验证路径代理到 AutoSSL 的挑战服务:
# Nginx 配置:把 ACME 验证请求代理到 AutoSSL 的 8082 端口
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
proxy_pass http://127.0.0.1:8082;
}
location / {
return 301 https://$host$request_uri;
}
}
8.2 DNS-01 方式(API 自动解析)
适合通配符证书(*.example.com)或 80 端口不开放的场景。先在 Web 界面的「凭证」里添加 DNS 服务商的 API 凭证,再申请证书时选 DNS-01 并关联凭证,AutoSSL 会自动添加、删除 TXT 记录,全程不用人工干预。
| 提供商 | 凭证字段 | 说明 |
|---|---|---|
| 阿里云 DNS(alidns) | AccessKey ID / AccessKey Secret | 在阿里云控制台创建 RAM 子账号并授权 AliyunDNSFullAccess |
| Cloudflare | API Token(或 Email + Global API Key) | 推荐用 API Token,只给 DNS 编辑权限 |
| DNSPod | ID / Key | DNSPod 旧版 API |
| 腾讯云 DNS(tencentcloud) | SecretId / SecretKey | DNSPod 新版 API;在腾讯云控制台「访问管理 > 访问密钥 > API 密钥管理」获取 |
注意 DNSPod(旧版 API)与腾讯云 DNS(新版 API)是两个不同的提供商类型,都保留在系统里,按你的域名实际托管在哪里选。凭证的权限越小越好:只需要 DNS 记录的读写权限,不要给账号的全部权限。
8.3 DNS-01 手动解析(无 API 密钥)
- 申请证书时验证方式选 DNS-01,使用方式选「手动解析(无 API 密钥)」,不需要选凭证、不需要填任何 key。
- 提交后证书状态变为「等待手动解析」,在证书列表点「手动解析」,查看待添加的 TXT 记录(记录名、记录值,可一键复制)。
- 去你的 DNS 服务商处手动添加这条 TXT 记录,然后点「已添加,继续验证」,服务端才会去校验。
- 等待确认最长 30 分钟,超时后签发失败,需重新申请或续期;服务重启会中断等待中的确认,证书会置为错误状态,同样需要重新申请或续期。
- 自动续期时同样要走确认流程:到期前自动续期开始后,证书会进入「等待手动解析」状态,在列表中确认即可。
九、自动续期、批量续期与到期通知
Let's Encrypt 证书有效期 90 天,ZeroSSL 也是 90 天,靠手动续期迟早会忘。AutoSSL 每天凌晨 3 点自动检查所有证书,到期的自动续期;也可以在证书列表手动点「续期」,或多选后一键「批量续期」。
到期通知是另一层保险:每天早上 8 点自动检查,30 天内到期的证书会通过飞书机器人或邮件发通知。两种通知方式可以同时配:
# 飞书机器人:先在飞书群里添加一个 Webhook 机器人
FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/xxxxx
# 邮件通知:配好 SMTP 后,通知会发到 NOTIFY_TO 的邮箱
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=user@example.com
SMTP_PASSWORD=your-password
SMTP_FROM=autossl@example.com
NOTIFY_TO=admin@example.com,ops@example.com
手动触发一次续期检查(不用等凌晨 3 点),可以用 API:
# 先登录拿 token
TOKEN=$(curl -s -X POST http://localhost:8080/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"your-strong-password"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")
# 触发续期检查
curl -s http://localhost:8080/api/renewals/check \
-H "Authorization: Bearer $TOKEN"
十、API 接口详解
除了 Web 界面,AutoSSL 还提供 REST API,方便写脚本批量管理。除了 /api/health 与 /api/login,其他接口都需要在请求头里带 Authorization: Bearer <token>。
| 方法 | 路径 | 说明 | 需要认证 |
|---|---|---|---|
| GET | /api/health | 健康检查 | 否 |
| POST | /api/login | 用户登录 | 否 |
| POST | /api/auth/change-password | 修改管理员密码 | 是 |
| GET | /api/stats | 统计信息 | 是 |
| GET | /api/certificates | 证书列表 | 是 |
| GET | /api/certificates/:id | 证书详情 | 是 |
| GET | /api/certificates/:id/detail | 证书解析详情(SAN/颁发者/有效期/序列号) | 是 |
| POST | /api/certificates | 申请证书 | 是 |
| PUT | /api/certificates/:id | 更新证书配置 | 是 |
| DELETE | /api/certificates/:id | 删除证书 | 是 |
| POST | /api/certificates/:id/renew | 手动续期 | 是 |
| POST | /api/certificates/batch-renew | 批量续期(ids 列表) | 是 |
| GET | /api/certificates/:id/files | 查看证书文件 | 是 |
| GET | /api/certificates/:id/manual-challenge | 查询待确认的手动 DNS-01 TXT 记录 | 是 |
| POST | /api/certificates/:id/manual-challenge/confirm | 确认已手动添加 TXT 记录,继续验证 | 是 |
| GET | /api/renewals/check | 触发续期检查 | 是 |
# 查证书列表
curl -s http://localhost:8080/api/certificates \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# 手动续期某张证书(id 换成实际值)
curl -s -X POST http://localhost:8080/api/certificates/1/renew \
-H "Authorization: Bearer $TOKEN"
# 批量续期
curl -s -X POST http://localhost:8080/api/certificates/batch-renew \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"ids":[1,2,3]}'
十一、证书文件与 Nginx 接入
申请成功后,证书文件存在 ./data/certs/<domain>/ 目录下:
fullchain.pem:完整证书链,Nginx 的 ssl_certificate 就用这个privkey.pem:私钥,Nginx 的 ssl_certificate_key 用这个,权限要收紧,不要让其他用户读取chain.pem:CA 证书链
# Nginx 引用 AutoSSL 签发的证书
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /opt/autossl/data/certs/example.com/fullchain.pem;
ssl_certificate_key /opt/autossl/data/certs/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
# 证书续期后需要 reload Nginx 才能生效,可以写个定时任务
# 每天凌晨 4 点 reload 一次(AutoSSL 是凌晨 3 点检查续期)
0 4 * * * /usr/sbin/nginx -t && /usr/sbin/nginx -s reload
注意 AutoSSL 续期后只更新了证书文件,不会自动 reload 你的 Nginx 或其他服务,需要自己加定时任务或钩子。上面这个 cron 每天凌晨 4 点检查配置并 reload,配合凌晨 3 点的续期检查,续期当天就能生效。
十二、常见问题
| 问题 | 原因与处理 |
|---|---|
| HTTP-01 验证失败 | 域名没解析到本机、80 端口不通、或 Nginx 没把 /.well-known/acme-challenge/ 代理到 8082;先 curl http://域名/.well-known/acme-challenge/test 验证路径是否通 |
| DNS-01 验证失败 | TXT 记录没生效(DNS 有传播延迟,等几分钟再确认)、凭证权限不足、或记录名填错;手动模式下确认前先用 dig -t txt _acme-challenge.域名 查一下记录是否存在 |
| 登录后很快又要重新登录 | JWT_SECRET 没设置固定值,服务重启后随机生成,旧 token 失效;在环境变量里设置固定 JWT_SECRET |
| 找不到管理员密码 | 没设置 ADMIN_PASSWORD 时随机密码只打印在首次启动日志里,用 docker logs autossl 或 journalctl -u autossl 查看;登录后在右上角菜单改密码 |
| 手动解析超时失败 | 等待确认最长 30 分钟,超时需重新申请或续期;服务重启会中断等待中的确认,证书置为错误状态,同样需要重新申请 |
| 续期后网站还是旧证书 | AutoSSL 只更新证书文件,不自动 reload Nginx 等服务,需要自己加定时任务 reload |
| ZeroSSL 申请失败 | ZeroSSL 需要 EAB Key ID 与 HMAC Key,申请时填上;在 ZeroSSL 官网开发者页面获取 EAB 凭证 |
十三、运维建议
- 备份 ./data 目录:证书、私钥、ACME 账号、凭证配置都在 ./data 里,定期备份,丢了私钥就得重新申请。
- 固定 JWT_SECRET 与 ADMIN_PASSWORD:生产环境不要依赖随机生成的值,写进 .env 或 compose 文件集中管理。
- 证书文件权限:privkey.pem 只给运行用户读(chmod 600),不要放进公开目录。
- 监控续期:除了飞书与邮件通知,建议再接一层外部监控(如 Uptime Kuma 检查证书有效期),双保险。
- 80 端口常开:HTTP-01 验证与续期都需要 80 端口,防火墙放行 80/TCP,不要只开 443。
- 不要两个工具同时管同一张证书:certbot 与 AutoSSL 同时续期同一域名会互相干扰,选定一个工具管到底。
- 升级:拉新代码后重新 docker compose up -d --build,数据在 volume 里不受影响;升级前先备份 ./data。
AutoSSL 把证书这件“三个月一次、每次都容易忘”的事变成了装好就不用管的自动化服务:Docker 一行命令启动,Web 界面点几下申请证书,凌晨自动续期,到期前飞书或邮件提醒。对于个人博客、小团队的多域名站点,它比 certbot 更省心,比面板更轻量。如果你手里有几台服务器的证书要管,先从一台开始试点,跑顺了再全量迁移。






