Docker 部署 Certbot + Nginx 自动申请与续期 HTTPS
Docker 部署 Certbot + Nginx 自动申请与续期 HTTPS
思路
- Certbot 用 webroot 模式 :Let’s Encrypt 访问
http://域名/.well-known/acme-challenge/,Nginx 从共享目录./www提供验证文件。 - 证书存放在
./certbot/conf,Nginx 和 Certbot 两个容器共享。 - Certbot 容器每 12 小时执行一次
renew,Nginx 每 12 小时 reload 一次以加载新证书。
前置条件
- 域名 A 记录已解析到服务器公网 IP。
- 服务器的 80、443 端口对公网开放。
./conf/nginx.conf的http {}块里有include /etc/nginx/conf.d/*.conf;。
第 1 步:修改 docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '3'
services:
mynginx:
image: nginx
container_name: mynginx
ports:
- "80:80"
- "443:443"
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config:/etc/nginx/conf.d
- ./tcpconfig:/etc/nginx/tcp.d
- ./ssl:/etc/nginx/ssl
- ./www:/usr/share/nginx/html
- ./certbot/conf:/etc/letsencrypt:ro # 新增:证书目录
# 新增:每 12 小时 reload 一次,加载续期后的新证书
command: /bin/sh -c 'while :; do sleep 12h & wait $${!}; nginx -s reload; done & exec nginx -g "daemon off;"'
restart: always
certbot: # 新增
image: certbot/certbot
container_name: certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./www:/usr/share/nginx/html
entrypoint: /bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /usr/share/nginx/html; sleep 12h & wait $${!}; done;'
restart: always
第 2 步:先放一个仅 HTTP 的站点配置
此时证书还不存在,如果直接写 443 的配置,Nginx 会启动失败。
./config/example.com.conf:
1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
}
location / {
return 200 'ok';
}
}
启动 Nginx:
1
2
mkdir -p certbot/conf www
docker compose up -d mynginx
第 3 步:首次申请证书
建议先用 --dry-run 测试,避免触发 Let’s Encrypt 的频率限制:
1
2
3
4
5
docker compose run --rm --entrypoint certbot certbot certonly \
--webroot -w /usr/share/nginx/html \
-d example.com -d www.example.com \
--email you@example.com --agree-tos --no-eff-email \
--dry-run
测试通过后去掉 --dry-run 再执行一次。成功后可以看到:
1
2
ls certbot/conf/live/example.com/
# fullchain.pem privkey.pem ...
第 4 步:改成 HTTPS 配置
替换 ./config/example.com.conf:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on; # nginx >= 1.25.1;旧版本写成 listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
location / {
# 按需改成你的后端,例如:
# proxy_pass http://host.docker.internal:5000;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
root /usr/share/nginx/html;
index index.html;
}
}
检查配置并重载:
1
docker exec mynginx nginx -t && docker exec mynginx nginx -s reload
然后启动 certbot 续期容器:
1
docker compose up -d
第 5 步:验证
1
2
3
4
curl -I https://example.com
# 模拟续期(不会真的签发)
docker compose run --rm --entrypoint certbot certbot renew --dry-run
补充说明
- 新增域名 :重复第 2 到第 4 步,每个新域名先放 HTTP 配置,再用
certonly申请,最后改成 HTTPS 配置。 - 后端是另一个 compose 里的服务 :把它们加入同一个 Docker network,
proxy_pass用容器名。 - 泛域名证书(
*.example.com) :webroot 方式不支持,需要 DNS-01 验证,要用对应 DNS 厂商的 certbot 插件,例如certbot/dns-cloudflare、certbot-dns-aliyun等。 - 续期时机 :certbot 只会在证书剩余不足 30 天时才真正续期,每 12 小时检查一次属正常频率。
./ssl目录 :原来的./ssl挂载可以保留给手动证书使用,Let’s Encrypt 证书不依赖它。- 日志排查 :
docker logs certbot,证书申请失败多半是 80 端口不通或域名解析未生效。
本文由作者按照 CC BY 4.0 进行授权