SSL 憑證自動更新設定指南
問題背景
Let’s Encrypt SSL 憑證有效期為 90 天,需要定期更新。當 Nginx 運行在 Docker 容器內時,certbot 預設的 standalone 模式會因為 port 80 被佔用而無法更新憑證。
解決方案:Webroot 模式
使用 webroot 模式讓 certbot 透過檔案驗證來更新憑證,不需要停止 Nginx 服務。
運作原理
- Let’s Encrypt 會訪問
http://your-domain/.well-known/acme-challenge/{token} - Certbot 在指定目錄建立驗證檔案
- 驗證成功後更新憑證
- Nginx reload 載入新憑證
設定步驟
1. 建立 certbot 驗證目錄
sudo mkdir -p /var/www/certbot/.well-known/acme-challenge
sudo chown -R root:root /var/www/certbot
sudo chmod -R 755 /var/www/certbot
2. Nginx 設定
確保 Nginx 設定檔包含 Let’s Encrypt 驗證路徑:
server {
listen 80;
server_name your-domain.com;
# Let's Encrypt challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirect all HTTP to HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
3. Docker Compose 設定
在 nginx service 加入 certbot 目錄掛載:
nginx:
volumes:
- ./docker/nginx/production.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- /var/lib/letsencrypt:/var/lib/letsencrypt:ro
- /var/www/certbot:/var/www/certbot:ro # 新增這行
4. Certbot renewal 設定檔
位置:/etc/letsencrypt/renewal/your-domain.conf
正確格式範例:
# renew_before_expiry = 30 days
version = 2.9.0
archive_dir = /etc/letsencrypt/archive/your-domain.com
cert = /etc/letsencrypt/live/your-domain.com/cert.pem
privkey = /etc/letsencrypt/live/your-domain.com/privkey.pem
chain = /etc/letsencrypt/live/your-domain.com/chain.pem
fullchain = /etc/letsencrypt/live/your-domain.com/fullchain.pem
# Options used in the renewal process
[renewalparams]
account = your-account-id
authenticator = webroot
webroot_path = /var/www/certbot
server = https://acme-v02.api.letsencrypt.org/directory
key_type = ecdsa
[[webroot_map]]
your-domain.com = /var/www/certbot
注意: server 和 key_type 必須在 [[webroot_map]] 之前,順序很重要!
5. 設定自動更新 Cron Job
sudo crontab -e
加入:
0 3 * * * certbot renew --quiet --deploy-hook "docker exec kg_nginx nginx -s reload"
常用指令
| 用途 | 指令 |
|---|---|
| 檢查憑證狀態 | sudo certbot certificates |
| 測試更新 | sudo certbot renew --dry-run |
| 手動更新 | sudo certbot renew |
| 強制重新申請 | sudo certbot certonly --webroot -w /var/www/certbot -d your-domain.com --force-renewal |
| 查看誰佔用 Port 80 | sudo lsof -i :80 |
疑難排解
Port 80 already in use
- 原因: Docker 容器佔用了 port 80
- 解決: 使用 webroot 模式,不需要停止服務
renewal config file is broken / parsefail
- 原因: 設定檔格式錯誤
- 檢查: authenticator 是否重複、webroot_path 後面是否多逗號、參數順序是否正確
No such file or directory
- 原因: certbot 目錄不存在或權限不足
- 解決:
sudo mkdir -p /var/www/certbot/.well-known/acme-challenge && sudo chmod -R 755 /var/www/certbot
緊急修復(憑證已過期)
docker stop kg_nginx
sudo certbot renew
docker start kg_nginx
架構圖
┌─────────────────┐
│ Let's Encrypt │
└────────┬────────┘
│ GET /.well-known/acme-challenge/{token}
▼
┌──────────────────────────────────────────────────────────┐
│ Host Server │
│ │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ Certbot │ │ Docker (kg_nginx) │ │
│ │ │ │ ┌──────────────────┐ │ │
│ │ writes token │ │ │ Nginx │ │ │
│ │ │ │ │ │ │ │ │
│ │ ▼ │ mount │ │ serves challenge │ │ │
│ │/var/www/certbot ◄─────►│ │ from /var/www/ │ │ │
│ │ │ │ │ certbot │ │ │
│ └──────────────┘ │ └──────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
學到的重點
- Let’s Encrypt 憑證有效期 90 天,必須設定自動更新
- Standalone vs Webroot 模式: Docker 環境下應使用 webroot 模式避免 port 衝突
- Certbot 設定檔順序很重要:
[renewalparams]的參數必須在[[webroot_map]]之前 - Docker volume 掛載: 讓容器內的 Nginx 可以存取 host 上的驗證檔案
- Cron job + deploy-hook: 自動更新後自動 reload Nginx 載入新憑證