Получение и ротация SSL сертификатов с Certbot в Docker
Certbot не нужно держать как постоянный daemon. Запускаем one-shot контейнер по cron — меньше ресурсов, проще отлаживать.
Схема работы
- Nginx слушает порт 80, отдаёт ACME challenge из общей директории
- Certbot запускается через
docker compose run --rm, проверяет/обновляет сертификат - После обновления nginx перечитывает конфиг через
SIGHUP - Всё управляется одной cron-задачей
Подготовка docker-compose.yml
Certbot — сервис с профилем, не стартует при docker compose up:
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
certbot:
image: certbot/certbot:latest
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
profiles:
- certbotprofiles: [certbot]— контейнер не стартует автоматически, только черезdocker compose run --rm certbotили--profile certbot- Nginx монтирует
certbot/confкак read-only (:ro), certbot — с записью certbot/www— общая директория для ACME webroot challenge
Конфиг nginx для ACME challenge
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}Первичное получение сертификата
SSL-сертификата ещё нет, HTTPS-блок nginx не заработает. Порядок:
1. Запустить временный nginx только с HTTP:
# Временный конфиг без HTTPS-блока
docker run -d --name nginx-init \
-p 80:80 \
-v ./nginx-init.conf:/etc/nginx/conf.d/default.conf:ro \
-v ./certbot/www:/var/www/certbot:ro \
nginx:1.27-alpine2. Получить сертификат:
docker run --rm \
-v ./certbot/conf:/etc/letsencrypt \
-v ./certbot/www:/var/www/certbot \
certbot/certbot:latest \
certonly --webroot \
--webroot-path=/var/www/certbot \
--email your@email.com \
--agree-tos \
--no-eff-email \
-d example.com3. Убрать временный контейнер и запустить полный стек:
docker rm -f nginx-init
docker compose up -dАвтоматическая ротация
Скрипт обновления
/opt/project/ssl_renew.sh:
#!/bin/bash
cd /opt/project
docker compose run --rm certbot renew --no-random-sleep-on-renew \
&& docker compose kill -s SIGHUP nginxrun --rm— запустить, выполнить, удалить контейнерcertbot renew— обновляет только если до истечения менее 30 дней--no-random-sleep-on-renew— без случайной задержки, мы и так по cronSIGHUP nginx— nginx перечитывает конфиг без downtime
Cron
# Ежедневно в 3:00 — проверка и обновление SSL
0 3 * * * /opt/project/ssl_renew.sh >> /opt/project/log/ssl_renew.log 2>&1Certbot сам проверяет срок — если до истечения больше 30 дней, он ничего не делает. Безопасно запускать ежедневно.
Почему не daemon-контейнер
Часто встречается вариант с вечно работающим certbot:
# Не рекомендуется
entrypoint: "/bin/sh -c 'while :; do certbot renew; sleep 12h; done'"Минусы: жрёт память постоянно (~50-100 MB), логи внутри контейнера, при падении sleep-цикла обновление тихо прекращается. One-shot через cron проще и надёжнее.