树莓派 Docker 化网页服务器(5):acme.sh 容器化 —— HTTPS 证书的自动续签
前言
本专栏第4篇中,我们完成了 ddns-go 的容器化部署,pi-ddns.jiejinx.cn 已能正确解析到树莓派的公网 IPv6 地址。本篇我们继续推进,加入 HTTPS 证书服务 —— acme.sh。
旧专栏第6篇 中,我们安装 acme.sh、配置阿里云 DNS API、签发泛域名证书、挂载到 Nginx,一步步完成了 HTTPS 证书的自动申请与续签。那是树莓派拥有可信证书的开端。
这一篇,我们做同样的事 —— 让 pi-ddns.jiejinx.cn 拥有受浏览器信任的 HTTPS 证书 —— 但换一种方式:直接以容器化部署 acme.sh,跳过下载脚本、配置环境变量、手动挂载证书这些步骤。拉取镜像 → 创建共享卷 → 签发证书 → 挂载到 Nginx,四步到位。
acme.sh 容器化之后,后续的 frpc 也会遵循同样的模式。每个组件都以容器形态加入,最终汇聚到一个 Compose 文件中。
一、原理
1. acme.sh 的作用
acme.sh 是一个纯 Shell 编写的 ACME 协议客户端,用于从 Let's Encrypt(或其他 ACME CA)免费申请和自动续签 SSL/TLS 证书。它的工作流程是:
- 通过 DNS API 验证域名所有权
- 向 Let's Encrypt 提交证书签名请求
- 下载证书文件(完整证书链 + 私钥)
- 在证书到期前自动续签
简单说:你不需要手动申请证书、上传文件、记住续签日期,acme.sh 保证证书永不过期。
2. 容器化前后对比
| 维度 | 裸机运行 | Docker 容器化 |
|---|---|---|
| 安装方式 | 下载 shell 脚本执行 | docker pull |
| 配置位置 | ~/.acme.sh/ 或 /root/.acme.sh/ | ~/docker/acme.sh/ 统一管理 |
| API 凭证 | 写入 account.conf | 环境变量传入 |
| 自启管理 | crontab 定时任务 | restart: unless-stopped + daemon 模式 |
| 升级方式 | acme.sh --upgrade | docker compose pull && up |
| 证书共享 | 手动复制或软链到 Nginx 目录 | Docker volume 自动挂载 |
| 卸载残留 | 需手动清理脚本和配置 | 删容器 + 删 volume,干干净净 |
3. 为什么必须用 DNS API 模式
Let's Encrypt 提供两种域名验证方式:
- HTTP-01:在
http://<domain>/.well-known/acme-challenge/放置 token 文件,CA 通过 HTTP 访问验证。要求服务器开放 80 端口且公网可达。 - DNS-01:在域名的
_acme-challengeTXT 记录中写入 token,CA 通过 DNS 查询验证。只需 DNS 服务商的 API 凭证,不依赖任何端口。
家用宽带封禁 80/443 端口,HTTP-01 模式无法工作。因此只能使用 DNS-01 模式,通过阿里云 DNS API 验证域名所有权。
4. 关于端口选择的说明
家用宽带通常封禁 80 和 443 端口,外部 HTTPS 请求无法通过标准端口到达树莓派。因此本篇沿用旧专栏的方案,将 HTTPS 服务部署在 9133 端口。
访问方式变为 https://pi-ddns.jiejinx.cn:9133,而非默认的 https://pi-ddns.jiejinx.cn。这对 acme.sh 的工作没有影响 —— 它只关心域名所有权验证(通过 DNS API),端口是 Nginx 的职责。
后续 frpc 容器化后,可通过 frp 隧道将公网服务器的 443 端口流量转发到树莓派的 9133 端口,届时在第七篇的整合 Compose 中统一处理。
5. Docker volume 机制说明
Docker volume 是 Docker 管理持久化数据的一种方式。与 bind mount(如 ~/docker/nginx/conf:/etc/nginx/conf.d)不同,volume 的生命周期由 Docker 自身管理,不依赖宿主机上的特定目录路径。
docker volume create certs 这条命令做了三件事:
- 在 Docker 的数据目录(默认
/var/lib/docker/volumes/)下创建一块独立的存储空间 - 给它命名为
certs,后续容器通过这个名字引用它 - 初始化空的
_data目录,等待容器写入数据
多个容器可以挂载同一个 named volume,实现数据共享。在本篇中:
acme.sh容器将certs挂载到/acme.sh,证书写入该目录Nginx容器将certs挂载到/etc/nginx/certs,从该目录读取证书
两个容器各自只关心自己的挂载点路径,不需要知道对方的存在,也不需要知道证书在宿主机上的具体位置。
6. 两步走的设计思路
本篇分两步完成 acme.sh 容器化,不是重复,而是分工不同:
第一步:一次性容器签发证书
docker run --rm ... --issue --dns dns_ali -d jiejinx.cn -d '*.jiejinx.cn'--rm容器执行完自动删除- 任务是首次向 Let's Encrypt 申请证书,涉及 DNS 验证、域名所有权确认、CA 交互
- 证书签发成功后容器退出,使命完成
第二步:常驻容器自动续签
docker run -d ... daemon-d后台长期运行- 任务是每天检查证书有效期,到期前自动续签
- 不重新走 DNS 验证流程,只在需要续签时才调用 API
为什么不能合二为一?
acme.sh 的 daemon 模式只会检查已有证书的有效期,不会主动发起首次签发。如果直接用 daemon 模式启动一个没有证书的环境,它会一直空转,永远不会去申请第一张证书。
因此标准流程就是:先一次性签发拿到第一张证书,再用 daemon 容器长期守护续签。 两个步骤缺一不可。
二、操作步骤
1. 建目录
SSH 登录树莓派,创建 acme.sh 的专用目录:
mkdir -p ~/docker/acme.sh
cd ~/docker/acme.sh这个目录将存放 Compose 文件和环境变量文件。后续所有操作都在此目录下进行。
2. 创建共享 volume
创建一个名为 certs 的 Docker volume,acme.sh 和 Nginx 都将挂载它:
docker volume create certs3. 拉取镜像
acme.sh 官方镜像支持多架构,树莓派 4B(arm64) 会自动拉取对应版本:
docker pull neilpang/acme.sh:latest利用第3篇配置的阿里云镜像加速器,拉取过程通常在几秒到半分钟内完成。
若拉取过程中断,
Docker会自动处理不完整的层,再次执行docker pull即可继续下载,无需额外清理。
4. 首次申请证书
运行一个一次性容器来签发证书。使用阿里云 DNS API 模式,签发主域名 jiejinx.cn 和泛域名 *.jiejinx.cn:
docker run --rm \
-v certs:/acme.sh \
-e Ali_Key='你的阿里云AccessKey ID' \
-e Ali_Secret='你的阿里云AccessKey Secret' \
neilpang/acme.sh:latest \
--issue --server letsencrypt --dns dns_ali -d jiejinx.cn -d '*.jiejinx.cn'参数说明:
| 参数 | 作用 |
|---|---|
--rm | 容器执行完毕后自动删除 |
-v certs:/acme.sh | 将证书输出到共享 volume certs,挂载到容器内的 /acme.sh 目录 |
-e Ali_Key / Ali_Secret | 传入阿里云 API 凭证 |
--server letsencrypt | 显式指定 Let's Encrypt CA,避免 ZeroSSL 的 EAB 问题 |
--dns dns_ali | 使用阿里云 DNS API 验证 |
-d jiejinx.cn -d '*.jiejinx.cn' | 签发主域名和泛域名 |
执行成功后,日志末尾会显示证书文件的存放路径:
Your cert is in: /acme.sh/jiejinx.cn_ecc/jiejinx.cn.cer
Your cert key is in: /acme.sh/jiejinx.cn_ecc/jiejinx.cn.key
The intermediate CA cert is in: /acme.sh/jiejinx.cn_ecc/ca.cer
And the full-chain cert is in: /acme.sh/jiejinx.cn_ecc/fullchain.cer关于自检过程中的 libcurl 错误:
acme.sh在验证 DNS 记录时会通过公共 DNS 服务器回查 TXT 记录是否正确添加。家庭宽带环境下,可能出现libcurl error 35/28提示,这是 DNS 查询偶发的 SSL 握手超时,acme.sh会自动重试,不影响签发。旧专栏对此有详细说明,此处不再赘述。
5. 验证证书文件
借助一个极小的 alpine 容器,查看 certs volume 中的证书文件:
docker run --rm -v certs:/acme.sh alpine ls -la /acme.sh/jiejinx.cn_ecc/输出示例:
total 40
drwxr-xr-x 2 root root 4096 Sep 1 12:17 .
drwx---rwx 4 1000 1000 4096 Sep 1 12:15 ..
-rw-r--r-- 1 root root 3523 Sep 1 12:17 ca.cer
-rw-r--r-- 1 root root 4871 Sep 1 12:17 fullchain.cer
-rw-r--r-- 1 root root 1324 Sep 1 12:17 jiejinx.cn.cer
-rw------- 1 root root 675 Sep 1 12:17 jiejinx.cn.conf
-rw-r--r-- 1 root root 528 Sep 1 12:15 jiejinx.cn.csr
-rw-r--r-- 1 root root 258 Sep 1 12:15 jiejinx.cn.csr.conf
-rw------- 1 root root 260 Sep 1 12:15 jiejinx.cn.key四个关键文件均已就位:
fullchain.cer—— 完整证书链(Nginx 使用)jiejinx.cn.key—— 私钥(Nginx 使用)ca.cer—— CA 中间证书jiejinx.cn.cer—— 域名证书
alpine 只是一个临时查看工具,确认文件齐全后将其删除:
docker rmi alpine6. 启动常驻续签容器
首次签发是一次性操作,证书到期后还需要自动续签。为此,启动一个常驻容器,它以 daemon 模式运行,每天检查证书有效期:
docker run -d \
--name rpi-acme-sh \
--restart unless-stopped \
-v certs:/acme.sh \
-e Ali_Key='你的阿里云AccessKey ID' \
-e Ali_Secret='你的阿里云AccessKey Secret' \
neilpang/acme.sh:latest \
daemon查看启动日志,确认 supercronic 定时任务已就绪:
docker logs --tail 5 rpi-acme-sh输出示例:
Running Supercronic using crontab at /acme.sh/crontab
time="2026-09-01T12:29:58Z" level=info msg="reaping dead processes"
time="2026-09-01T12:29:58Z" level=info msg="read crontab: /acme.sh/crontab"7. 修改 Nginx 配置
acme.sh 容器已将证书写入 certs volume,现在需要让 Nginx 容器也挂载这个 volume,并配置 HTTPS 站点。
① 停掉旧容器
第3篇中 Nginx 使用 docker run 启动,也提供了Compose 启动方式,若尚未 Compose 化,则先停掉旧容器:
docker stop rpi-nginx && docker rm rpi-nginx如果你的
Nginx已做过Compose化,则直接在docker-compose.yml中修改即可,无需此步骤。
② 创建 docker-compose.yml
cd ~/docker/nginx
nano docker-compose.yml粘贴以下内容:
services:
nginx:
image: arm64v8/nginx:stable
container_name: rpi-nginx
restart: always
ports:
- "9133:9133"
volumes:
- ./conf:/etc/nginx/conf.d:ro
- ./html:/usr/share/nginx/html:ro
- ./logs:/var/log/nginx
- certs:/etc/nginx/certs:ro
volumes:
certs:
external: true保存退出(Ctrl+O→ Enter→ Ctrl+X)。
相比第三篇的改动:
ports从"80:80" "443:443"改为"9133:9133",因家宽封禁标准端口- 新增
certs:/etc/nginx/certs:ro,挂载共享 volume,只读模式 - 顶层新增
volumes: certs: external: true,引用已创建的certsvolume - 删除了旧的
./ssl:/etc/nginx/ssl:ro挂载(裸机时代的证书路径)
③ 新建站点配置文件
nano ~/docker/nginx/conf/pi-ddns.jiejinx.cn.conf粘贴以下内容:
server {
listen 9133 ssl http2;
listen [::]:9133 ssl http2;
server_name pi-ddns.jiejinx.cn;
ssl_certificate /etc/nginx/certs/jiejinx.cn_ecc/fullchain.cer;
ssl_certificate_key /etc/nginx/certs/jiejinx.cn_ecc/jiejinx.cn.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}保存退出(Ctrl+O→ Enter→ Ctrl+X)。
配置要点:
- 监听 9133 端口,启用 SSL 和 HTTP/2
server_name设为pi-ddns.jiejinx.cn,精确匹配- 证书路径指向
certsvolume 中的fullchain.cer和jiejinx.cn.key - 注意文件名是
.cer和.key,不是旧专栏的.pem
④ 启动新容器
docker compose up -d8. 验证 HTTPS 访问
在树莓派本机或同一局域网的电脑上执行:
curl -I https://pi-ddns.jiejinx.cn:9133应返回 HTTP/2 200:
HTTP/2 200
server: nginx/1.30.4
date: Tue, 01 Sep 2026 12:47:44 GMT
content-type: text/html
content-length: 741
last-modified: Fri, 21 Aug 2026 09:26:16 GMT
etag: "6a8819b8-2e5"
accept-ranges: bytes也可以在浏览器中直接访问 https://pi-ddns.jiejinx.cn:9133,应显示绿色锁标志,证书信息为 Let's Encrypt,域名匹配 pi-ddns.jiejinx.cn。
9. acme.sh 改用 Compose 管理
将 acme.sh 也纳入 Compose 管理体系,与 Nginx 统一管理。
① 创建 docker-compose.yml
cd ~/docker/acme.sh
nano docker-compose.yml粘贴以下内容:
services:
acme-sh:
image: neilpang/acme.sh:latest
container_name: rpi-acme-sh
restart: unless-stopped
command: daemon
volumes:
- certs:/acme.sh
environment:
- Ali_Key=${Ali_Key}
- Ali_Secret=${Ali_Secret}
volumes:
certs:
external: true保存退出(Ctrl+O→ Enter→ Ctrl+X)。
② 创建 .env 文件
在 ~/docker/acme.sh/ 目录下创建 .env 文件,存放阿里云 API 凭证(敏感信息,不要提交到 Git):
nano .env内容:
Ali_Key=你的阿里云AccessKey ID
Ali_Secret=你的阿里云AccessKey Secret保存退出(Ctrl+O→ Enter→ Ctrl+X)。
③ 切换至 Compose 管理
停掉当前容器,改用 Compose 启动:
docker stop rpi-acme-sh && docker rm rpi-acme-sh
docker compose up -d确认运行状态:
docker compose ps输出示例:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
rpi-acme-sh neilpang/acme.sh:latest "/entry.sh daemon" acme-sh 6 seconds ago Up 6 seconds查看启动日志,确认 supercronic 定时任务已就绪:
docker logs --tail 5 rpi-acme-sh输出示例:
Running Supercronic using crontab at /acme.sh/crontab
time="2026-09-01T12:29:58Z" level=info msg="reaping dead processes"
time="2026-09-01T12:29:58Z" level=info msg="read crontab: /acme.sh/crontab"至此,acme.sh 容器化部署完成。证书已签发、Nginx 已加载、续签守护已运行,全部纳入 Compose 管理。
三、总结
1. 新旧做法对比
| 维度 | 旧专栏(裸机) | 本专栏(容器化) |
|---|---|---|
| 安装 | 下载 shell 脚本执行 | docker pull |
| 配置 | 编辑 account.conf 写入 AK/SK | 环境变量传入 |
| 证书签发 | acme.sh --issue ... | 一次性容器执行相同命令 |
| 自启续签 | crontab 定时任务 | daemon 模式 + restart: unless-stopped |
| 证书共享 | 手动复制到 Nginx 目录 | Docker volume 自动挂载 |
| 升级 | acme.sh --upgrade | docker compose pull && up |
| 卸载 | 手动清理脚本、配置、crontab | 删容器 + 删 volume |
下篇预告
HTTPS 证书已经容器化,但还有一个组件正在路上 —— frpc。目前树莓派的家宽环境无法直接通过 80/443 端口对外提供服务,需要借助 frp 隧道穿透。
下一篇,我们将把 frpc 迁进容器,让内网穿透也纳入 Docker 管理体系。
届时,Nginx + ddns-go + acme.sh + frpc 四个核心服务全部容器化,离第七篇的终极目标 —— 一个 Compose 文件启动全家桶 —— 又近了一步。