前提条件
- 有现成的域名、公网IP
- 可访问公网的服务器
- 可用的邮箱
开始部署
1. Certbot 镜像拉取
docker pull certbot/certbot:v5.2.2
2. 创建 docker-compose.yml 文件
services:
nginx:
image: nginx:1.25.4
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro # 和服务器时间同步
- /root/docker/nginx/nginx/:/etc/nginx/ # 主配置目录
- /root/docker/nginx/nginx/certs/:/etc/letsencrypt # 证书配置目录
- /root/docker/nginx/html/:/usr/share/nginx/html # 网站根目录
- /root/docker/nginx/log/:/var/log/nginx # 日志目录
networks:
- nginx-network
restart: always
entrypoint: ["/docker-entrypoint.sh"]
command: ["nginx", "-g", "daemon off;"]
stop_signal: SIGQUIT
logging:
driver: json-file
networks:
- nginx-network
depends_on:
- certbot # 确保 Certbot 先启动(非必须)
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- /root/docker/nginx/nginx/certs/:/etc/letsencrypt # 证书存储目录(与 Nginx 共享)
- /root/docker/nginx/html/:/var/www/html # HTTP 验证所需的 Webroot
networks:
- nginx-network
restart: always
networks:
nginx-network:
external: true
driver: bridge
如果使用的是openresty,则使用下面的配置
services:
openresty:
container_name: openresty
deploy:
resources:
limits:
cpus: 5
memory: '256M'
image: registry.cn-qingdao.aliyuncs.com/ling/openresty:1.27.1.2-focal #替换为自身镜像
environment:
TZ: Asia/Shanghai
network_mode: host
restart: always
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d/:rw
- ./nginx/conf:/usr/local/openresty/nginx/conf/:rw
- ./nginx/html:/usr/local/openresty/nginx/html:rw
- ./nginx/logs:/usr/local/openresty/nginx/logs/:rw
- ./nginx/lua:/usr/local/openresty/nginx/modules/:rw
- ./nginx/certs:/usr/local/openresty/nginx/certs/:rw
certbot:
image: certbot/certbot:v5.2.2
container_name: certbot
volumes:
- ./nginx/certs/:/etc/letsencrypt # 证书存储目录(与 Nginx 共享)
- ./nginx/html/:/var/www/html # HTTP 验证所需的 Webroot
- ./nginx/logs/certbot/:/var/log/letsencrypt
network_mode: host
restart: on-failure:3
entrypoint: ["sh", "-c"]
# 避免容器启动后退出(休眠)
command: ["tail", "-f", "/dev/null"]
3. 修改 nginx.conf 或 创建 tansuo.conf 文件
server {
listen 80;
server_name tansuo.com; # 替换为你的域名
#配置http验证可访问
location ^~ /.well-known/acme-challenge/ {
#此目录都是nginx容器内的目录,对应宿主机volumes中的http验证目录,而宿主机的又与certbot容器中命令--webroot-path指定目录一致,从而就整个串起来了,解决了http验证问题
root /etc/letsencrypt; #openresty对应为/usr/local/openresty/nginx/html
}
# 其他请求重定向到HTTPS
location / {
return 301 https://$host$request_uri;
}
}
4. 启动服务
sudo docker compose -f docker-compose.yml up -d
5. 测试证书发放
运行以下命令测试证书发放是否正常:
sudo docker compose run --rm certbot certonly --webroot --webroot-path /etc/letsencrypt --dry-run -d tansuo.com
或者
docker compose run --rm certbot certonly --webroot --webroot-path /var/www/html --dry-run -d tansuo.com
如果输出 The dry run was successful.,说明测试成功。
如果出现::404字样,首先检查yml配置是否和文章中的匹配,其次检查域名和公网IP是否正确绑定。最后重启nginx服务。
6. 正式获取证书
如果测试成功,去掉 --dry-run 参数正式获取证书:
docker compose run --rm certbot certonly --webroot --webroot-path /etc/letsencrypt -d tansuo.com
按照提示输入邮箱等信息,完成证书申请。
7. 生成证书文件
Certbot 会在 ./nginx/certs/live/tansuo.com/ 目录下生成证书文件,包括:
fullchain.pem:完整的证书链privkey.pem:私钥
8. 创建 HTTPS 配置文件
在你自定义的配置文件,比如上文创建的tansuo.conf 中配置 HTTPS信息:
server {
listen 443 ssl;
server_name tansuo.com;
ssl_certificate /etc/letsencrypt/live/xxx.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/xxx.com/privkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api/ {
proxy_pass http://gateway:18080/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
将此文件挂载到 Nginx 的配置目录中,我的tansuo.conf位于conf.d目录下。
9. 重启 Nginx
重新加载 Nginx 配置以应用 HTTPS:
docker compose restart nginx
10. 配置定时任务自动续期
为了避免证书过期,创建一个检查脚本,设置定时任务为每月 1 号凌晨 2 点检查证书是否过期&自动续期证书&重启 Nginx: vi renew-certbot.sh
#!/bin/bash
# Certbot SSL 证书自动续期脚本(针对90天有效期优化)
# 证书有效期:90天,推荐每30天检查续期
echo "=== SSL 证书自动续期脚本开始执行 ==="
echo "执行时间: $(date)"
echo "证书类型: Let's Encrypt (90天有效期)"
# 设置变量
LOG_FILE="/var/log/certbot-renew.log"
COMPOSE_DIR="/root/docker"
# 记录开始时间
START_TIME=$(date +%s)
# 检查必要目录
if [ ! -d "$COMPOSE_DIR" ]; then
echo "错误: Docker Compose 目录不存在: $COMPOSE_DIR" | tee -a "$LOG_FILE"
exit 1
fi
# 切换到工作目录
cd "$COMPOSE_DIR" || {
echo "错误: 无法切换到目录 $COMPOSE_DIR" | tee -a "$LOG_FILE"
exit 1
}
echo "工作目录: $(pwd)" | tee -a "$LOG_FILE"
# 检查 Docker 服务
if ! systemctl is-active --quiet docker; then
echo "错误: Docker 服务未运行,尝试启动..." | tee -a "$LOG_FILE"
systemctl start docker
sleep 10
fi
# 检查证书过期时间(可选)
echo "检查证书状态..." | tee -a "$LOG_FILE"
docker compose run --rm certbot certificates 2>&1 | grep -E "(EXPIRED|VALID|Domains)" | tee -a "$LOG_FILE"
# 执行证书续期(--quiet 模式减少输出)
echo "开始证书续期检查..." | tee -a "$LOG_FILE"
docker compose run --rm certbot renew --quiet --non-interactive 2>&1 | tee -a "$LOG_FILE"
RENEW_RESULT=${PIPESTATUS[0]}
if [ $RENEW_RESULT -eq 0 ]; then
echo "✓ 证书续期检查完成" | tee -a "$LOG_FILE"
# 检查是否有证书被更新
if docker compose run --rm certbot certificates 2>&1 | grep -q "待续期"; then
echo "检测到证书需要更新,重启 Nginx..." | tee -a "$LOG_FILE"
# 重启 Nginx
docker compose restart nginx 2>&1 | tee -a "$LOG_FILE"
if [ ${PIPESTATUS[0]} -eq 0 ]; then
echo "✓ Nginx 重启成功" | tee -a "$LOG_FILE"
# 发送通知(可选)
echo "证书续期成功 - $(date)" >> /tmp/cert-renew-success.log
else
echo "⚠ Nginx 重启失败,请手动检查" | tee -a "$LOG_FILE"
fi
else
echo "ℹ 无需续期,证书仍在有效期内" | tee -a "$LOG_FILE"
fi
else
echo "✗ 证书续期失败,退出码: $RENEW_RESULT" | tee -a "$LOG_FILE"
# 可以在这里添加报警通知
exit $RENEW_RESULT
fi
# 计算执行时间
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "脚本执行完成,耗时: ${DURATION}秒" | tee -a "$LOG_FILE"
echo "完成时间: $(date)" | tee -a "$LOG_FILE"
echo "========================================" | tee -a "$LOG_FILE"
设置定时任务:
crontab -e
添加以下内容:
# Let's Encrypt 证书自动续期(90天有效期)
# 每月1号凌晨2点检查(足够安全)
0 2 1 * * /root/renew-certbot.sh >/dev/null 2>&1
# 日志清理(每月清理一次旧日志)
0 3 1 * * find /var/log -name "certbot-renew.*.log" -mtime +30 -delete
保存后,定时任务将自动运行。
总结
通过以上步骤,使用 Certbot 为你的网站配置了TLS证书,并设置了自动续期任务。确保网站始终安全可靠,避免因证书过期导致的服务中断。
无法使用80、443端口情况
如果公司网络无法开放80、443端口,可以使用DNS-01方式进行验证,具体步骤如下:
一、前提条件
- Certbot 5.2.2 + 阿里云 DNS 插件 0.38.1
- 域名管理平台账号密码(需要开通AccessKey)
- CDN(域名+端口配置)
1. 阿里云 RAM 子用户配置
- 已创建 RAM 子用户,授予
AliyunDNSFullAccess权限(仅 DNS 管理,最小权限); - 保存子用户的
AccessKey ID和AccessKey Secret
二、步骤 1:编写 Dockerfile(构建带阿里云插件的 Certbot 镜像)
基于 Certbot 5.2.2 官方镜像(Alpine 系统),安装兼容的 certbot-dns-aliyun==0.38.1
在 docker-compose.yml同级目录下创建 Dockerfile:
# 基础镜像:Certbot 5.2.2 官方镜像(Alpine系统,最小体积)
FROM certbot/certbot:v5.2.2
# 安装阿里云DNS插件依赖 + 插件本身(适配5.2.2)
RUN apk update && apk add --no-cache \
python3-dev \
py3-pip \
gcc \
musl-dev \
libffi-dev \
openssl-dev && \
# 安装指定版本插件(官网验证兼容Certbot 5.2.2)
pip3 install --no-cache-dir certbot-dns-aliyun==0.38.1 && \
# 删除编译依赖,减小镜像体积
apk del gcc musl-dev libffi-dev openssl-dev && \
# 清理缓存
rm -rf /var/cache/apk/*
# 容器启动后休眠(避免空命令报错,不影响证书操作)
CMD ["tail", "-f", "/dev/null"]
自行构建镜像,我是推送镜像到个人阿里云镜像仓库,然后给docker-compose.yml使用。
三、步骤 2:修改 docker-compose.yml
version: '3.8'
services:
certbot:
image: registry.cn-qingdao.aliyuncs.com/ling/certbot-aliyun-dns:1.0
volumes:
- ./nginx/certs/:/etc/letsencrypt # 证书存储目录(核心)
- ./nginx/html/:/var/www/html # Webroot(备用)
- ./nginx/logs/:/var/log/letsencrypt # 日志目录
# 网络模式:host(确保容器能访问阿里云DNS API)
network_mode: host
# 重启策略:失败仅重启3次
restart: on-failure:3
# 覆盖ENTRYPOINT为sh,所有命令由shell解析
entrypoint: ["sh", "-c"]
# 避免容器启动后退出(休眠)
command: ["tail", "-f", "/dev/null"]
四、步骤 3:配置文件+构建镜像 + 启动容器(验证基础环境)
# 1. 配置文件
在./nginx/certs目录下新建certbot-dns.ini文件
sudo vi ./nginx/certs/certbot-dns.ini
# 2. 输入上文新建RAM用户获取的key和secret:
dns_aliyun_access_key = LTAI5tMUHuXMBR
dns_aliyun_access_key_secret = juK8Q6ASdBX1iRBofLNF14T5
# 3. 设置Certbot强制要求的600权限(仅root可读可写)
sudo chmod 600 ./nginx/certs/aliyun-dns.ini
sudo chown root:root ./nginx/certs/aliyun-dns.ini
# 4. 启动Certbot容器
sudo docker compose up -d
# 5. 验证容器状态(输出 Up 即正常)
sudo docker compose ps
# 正常输出:certbot Up (healthy) ...
五、步骤 4:核心验证(确保插件 + 环境变量正常)
验证 1:插件是否安装成功
docker compose run --rm certbot "certbot plugins | grep -i aliyun"
✅ 正确输出(说明插件识别正常):
* dns-aliyun
Description: Obtain certificates using a DNS TXT record (if you are using Aliyun DNS).
Interfaces: Authenticator, Plugin
Entry point: EntryPoint(name='dns-aliyun', value='certbot_dns_aliyun.dns_aliyun:Authenticator', group='certbot.plugins')
六、步骤 5:DNS-01 验证(Dry-Run 测试,无风险)
先执行模拟验证(不生成真实证书),确保阿里云 DNS 权限、网络、插件均正常:
sudo docker compose run --rm certbot "certbot certonly \
--authenticator dns-aliyun \
--dns-aliyun-credentials /etc/letsencrypt/aliyun-dns.ini \
--dns-aliyun-propagation-seconds 60 \
--agree-tos \
--no-eff-email \
--email gentlelions@163.com \
-d official.acc.cn \
--dry-run
"
✅ 正确输出(说明 DNS 验证流程正常):
Account registered.
Requesting a certificate for official.acc.cn
Performing the following challenges:
dns-01 challenge for domain official.acc.cn
Waiting 60 seconds for DNS changes to propagate
Successfully authorized domain official.acc.cn
Dry run: skipping deploy hook.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled HTTPS on https://official.acc.cn
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
七、步骤 6:生成正式证书
删除 --dry-run 参数,执行正式证书生成:
sudo docker compose run --rm certbot "certbot certonly \
--authenticator dns-aliyun \
--dns-aliyun-credentials /etc/letsencrypt/aliyun-dns.ini \
--dns-aliyun-propagation-seconds 60 \
--agree-tos \
--no-eff-email \
--email gentlelions@163.com \
-d official.acc.cn \
"
✅ 验证证书是否生成:
ls -l ./nginx/certs/live/official.acc.cn/
正常输出(包含证书文件):
total 4
-rw-r--r-- 1 root root 692 Jan 30 15:00 README
lrwxrwxrwx 1 root root 44 Jan 30 15:00 cert.pem -> ../../archive/official.acc.cn/cert1.pem
lrwxrwxrwx 1 root root 45 Jan 30 15:00 chain.pem -> ../../archive/official.acc.cn/chain1.pem
lrwxrwxrwx 1 root root 49 Jan 30 15:00 fullchain.pem -> ../../archive/official.acc.cn/fullchain1.pem
lrwxrwxrwx 1 root root 47 Jan 30 15:00 privkey.pem -> ../../archive/official.acc.cn/privkey1.pem
八、步骤 7:配置自动续期
Let's Encrypt 证书有效期 90 天,需配置定时任务自动续期,续期后重启 Nginx(若使用 Nginx)。
1. 创建续期脚本
在 ./nginx 目录下创建 certbot-renew.sh:
#!/bin/bash
set -e # 遇到错误立即退出,避免无效执行
# ===================== 配置项(根据你的环境修改)=====================
# Certbot工作目录(与docker-compose.yml挂载路径一致)
CERTBOT_DIR="/docker/nginx"
# OpenResty容器名(替换为你实际的容器名,如openresty、nginx等)
OPENRESTY_CONTAINER="openresty"
# 日志文件路径(自动创建,记录续期过程)
RENEW_LOG="${CERTBOT_DIR}/renew.log"
# ===================================================================
# 确保日志目录存在
mkdir -p "${CERTBOT_DIR}"
# 记录脚本启动时间
echo -e "\n========================================" >> "${RENEW_LOG}"
echo "Certbot 续期脚本执行时间:$(date +'%Y-%m-%d %H:%M:%S')" >> "${RENEW_LOG}"
# 切换到Certbot工作目录
cd "${CERTBOT_DIR}" || {
echo "错误:无法进入Certbot工作目录 ${CERTBOT_DIR}" >> "${RENEW_LOG}"
exit 1
}
# 执行Certbot续期(仅续期<30天的证书),捕获退出码
# 退出码说明:
# 0 = 无证书需续期(所有证书有效)
# 1 = 有证书续期成功
# 2 = 续期失败(网络/权限/配置错误)
docker compose run --rm certbot "certbot renew \
--authenticator dns-aliyun \
--dns-aliyun-credentials /etc/letsencrypt/aliyun-dns.ini \
--dns-aliyun-propagation-seconds 60" >> "${RENEW_LOG}" 2>&1
RENEW_EXIT_CODE=$?
# 根据退出码执行不同逻辑
case ${RENEW_EXIT_CODE} in
0)
echo "结果:无证书需要续期(所有证书有效期>30天),无需重启OpenResty" >> "${RENEW_LOG}"
;;
1)
echo "结果:证书续期成功,开始重启OpenResty..." >> "${RENEW_LOG}"
# 重启OpenResty容器(仅续期成功时执行)
docker restart "${OPENRESTY_CONTAINER}" >> "${RENEW_LOG}" 2>&1
if [ $? -eq 0 ]; then
echo "成功:OpenResty 重启完成" >> "${RENEW_LOG}"
else
echo "错误:OpenResty 重启失败,请手动检查容器状态" >> "${RENEW_LOG}"
fi
;;
2)
echo "结果:证书续期失败!请查看日志 ${RENEW_LOG} 排查问题" >> "${RENEW_LOG}"
;;
*)
echo "结果:未知错误,退出码 ${RENEW_EXIT_CODE},请手动检查" >> "${RENEW_LOG}"
;;
esac
echo "脚本执行结束:$(date +'%Y-%m-%d %H:%M:%S')" >> "${RENEW_LOG}"
### 2. 赋予脚本执行权限
```bash
sudo chmod +x ./nginx/certbot-renew.sh
3. 添加系统定时任务(crontab)
# 编辑crontab
sudo crontab -e
添加以下内容(每月 1 号、15 号凌晨 3 点执行续期,避免证书过期):
# Certbot自动续期(每月1/15号凌晨3点执行)
0 3 1,15 * * /bin/bash /docker/nginx/certbot-renew.sh >> /docker/nginx/renew.log 2>&1
4. 验证定时任务
sudo crontab -l # 输出上述添加的内容即正常
九、故障排查
1. 续期失败?查看日志
# Certbot日志
cat ./nginx/logs/letsencrypt/letsencrypt.log
# 续期脚本日志
cat ./renew.log
2. 阿里云 DNS 权限问题?
- 确认 RAM 子用户已授予
AliyunDNSFullAccess(不是只读权限); - 确认域名
official.acc.cn归属该阿里云账号。
3. 容器网络不通?
- 检查
network_mode: host是否配置,或替换为network: bridge+ 端口映射; - 验证容器内能否访问阿里云 API:
docker compose run --rm certbot ping dns.aliyuncs.com。