Сертификат почти никогда не истекает в удобный момент. Он заканчивается в ночь с субботы на воскресенье, когда почтовый ящик пуст, а браузер посетителей уже рисует красную страницу и честно пишет "небезопасно". Ручной перевыпуск раз в три месяца выглядит простой задачей ровно до первого отпуска. Certbot снимает эту рутину с плеч: он выпускает сертификат, сам его продлевает и оставляет человеку только контрольные проверки. Ниже разобраны устройство инструмента, автоматическое продление на таймере, хуки перезагрузки веб-сервера и ошибки, которые чаще всего портят нервы при первом запуске.

Как Certbot хранит сертификаты и параметры их продления

Let's Encrypt выдаёт доменные сертификаты на 90 дней. Короткий срок задуман не ради мучений, а ради гигиены: у сертификата меньше времени на накопление проблем, а автоматическое продление заставляет настроить процесс один раз и больше к нему не возвращаться. Общается Certbot с центром сертификации по протоколу ACME: клиент доказывает контроль над доменом, центр подписывает сертификат. Классическое доказательство для публичного сайта - проверочный файл по пути /.well-known/acme-challenge, который центр забирает по 80 порту.

Все рабочие файлы Certbot держит в каталоге /etc/letsencrypt, и у каждого подкаталога своя роль. Папка accounts хранит ключ учётной записи и почтовый адрес для писем об истечении. Папка archive - все выпущенные версии сертификатов с номерами. Папка live - символические ссылки на актуальную версию, их и читает nginx. Папка renewal - конфигурации продления, маленькие файлы, в которых записано, как сертификат был выпущен: домены, плагин, сервер, тип ключа, хуки. Команда certbot renew потом просто проигрывает эти файлы заново.

sudo ls -l /etc/letsencrypt/live/example.com/
# /etc/letsencrypt/renewal/example.com.conf, фрагмент
version = 5.8.0
archive_dir = /etc/letsencrypt/archive/example.com
cert = /etc/letsencrypt/live/example.com/cert.pem
key_type = ecdsa

Файлы в live руками не трогают. При продлении в archive появляется версия с новым номером, а ссылки в live незаметно переключаются на неё. Прошлые версии остаются на месте, поэтому неудачная попытка не разрушает работающий сайт: старый сертификат никуда не девается. Для nginx нужны два файла: fullchain.pem в директиве ssl_certificate и privkey.pem в ssl_certificate_key. Второй файл - закрытый ключ, права на него держат строгими.

Установка Certbot и первый выпуск сертификата для сайта

Certbot распространяется тремя путями: пакетом из репозитория дистрибутива, snap-пакетом и через pip с pipx. Актуальная ветка 5.8.0 требует Python не ниже 3.10, поэтому в старых релизах дистрибутивов из репозиториев прилетает версия постарше; для базовых задач она тоже годится. В Debian и Ubuntu ставят так:

sudo apt update
sudo apt install certbot python3-certbot-nginx

Первый выпуск занимает одну команду:

sudo certbot --nginx -d example.com -d www.example.com

Certbot спросит почту для уведомлений, покажет условия и сам перепишет конфиг nginx: добавит блок ssl, пути к сертификату и ключу, готовые настройки из /etc/letsencrypt/options-ssl-nginx.conf, а в конце предложит перенаправление с http на https. С ветки 2.0 закрытые ключи по умолчанию выпускаются на эллиптических кривых: они короче и быстрее в рукопожатии. Древним клиентам, которым нужна только RSA, адресован флаг --key-type rsa.

Если конфигурацию веб-сервера трогать нельзя, проверку проходят файлом из корня сайта. Режим webroot кладёт проверочный файл в указанный каталог, nginx его просто отдаёт:

sudo certbot certonly --webroot -w /var/www/example \
  -d example.com -d www.example.com

Режим standalone поднимает собственный слушатель на 80 порту и годится для чистой машины без веб-сервера. Для тренировки к любой команде добавляют --staging: сертификат выпустит тестовый центр, браузеры ему не поверят, зато лимиты не пострадают. Флаг --cert-name задаёт имя каталога в live на случай, когда домены захочется держать раздельно.

Автоматическое продление через таймер systemd и задание cron

Команда certbot renew обходит все конфигурации в renewal и продлевает только те сертификаты, у которых прошла половина срока жизни. Для стандартных 90 дней окно открывается за 45 дней до конца. До ветки 4.0 порог был фиксированным и равнялся 30 дням, поэтому в старых руководствах гуляет именно эта цифра. Запуск renew хоть каждый день безвреден: до окна команда тихо завершится сообщением, что обновлять нечего, и не потратит ни одного лимита.

Пакет certbot в Debian и Ubuntu обычно приносит собственную автоматизацию: таймер systemd или задание в /etc/cron.d. Проверить наличие просто:

systemctl list-timers --all | grep certbot
ls /etc/cron.d/

Если автоматизации нет, её собирают руками. Простой вариант - строка cron с флагом -q, который глушит всё, кроме ошибок:

17 3 * * * root certbot -q renew

Основательный вариант - таймер systemd: он переживает выключенный сервер и дотягивает пропущенный запуск после старта:

# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Renew Let's Encrypt certificates

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew
# /etc/systemd/system/certbot-renew.timer
[Timer]
OnCalendar=*-*-* 03:17:00
RandomizedDelaySec=30min
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer

Современный Certbot продлевает сертификаты через механизм ARI: клиент сам узнаёт у центра оптимальное окно обновления, и такие заказы вообще не подпадают под недельные лимиты. Совпадение двух механизмов автоматизации при этом не опасно: если работают и ручное задание, и пакетный таймер, Certbot переживает ситуацию штатно, защита от параллельного запуска не даст продлевать одно и то же дважды.

Хуки Certbot для перезагрузки nginx после каждого обновления

Файлы на диске обновились, а браузер по-прежнему видит старый сертификат. Классика жанра: nginx загрузил ключи в память при старте и держит их до перечитывания конфигурации. Без перезагрузки продление превращается в фикцию, и при живых свежих файлах приходят письма об истечении. Разруливается это хуками.

Хук --deploy-hook запускается только после успешного продления и запоминается в конфигурации сертификата:

sudo certbot renew --deploy-hook "systemctl reload nginx"

Reload, а не restart: перечитывание конфигурации не рвёт открытые соединения, посетители ничего не заметят. Пары --pre-hook и --post-hook срабатывают до и после каждой попытки, независимо от результата. Типовой пример - освобождение 80 порта для standalone:

sudo certbot certonly --standalone -d example.com \
  --pre-hook "systemctl stop nginx" \
  --post-hook "systemctl start nginx"

Скрипты можно не прописывать в командах, а складывать в каталоги /etc/letsencrypt/renewal-hooks: pre, deploy и post. Любой исполняемый файл оттуда Certbot подхватывает сам, порядок запуска - алфавитный по имени файла. Это удобно, когда один хук обслуживает сразу все сертификаты сервера: перечитать конфигурацию postfix, пересобрать haproxy, уведомить дежурного. Отключается такое поведение флагом --no-directory-hooks. Любопытная деталь: контрольный прогон renew --dry-run при успехе тоже дёргает deploy-хуки, так что работу хука видно заранее, до боевого продления.

Проверка продления в тестовом режиме без расхода лимитов

Главный инструмент контроля - команда certbot renew --dry-run. Она проходит весь путь продления, но разговаривает с тестовым сервером и ничего не меняет на диске. Лимиты боевого центра не расходуются, слабые места видны заранее, deploy-хуки отрабатывают как в настоящем запуске, если прогон успешен.

sudo certbot renew --dry-run

Проверить можно и точечно, по имени сертификата:

sudo certbot renew --cert-name example.com --dry-run

Тестовый центр живёт по тем же правилам ACME, но с мягкими лимитами, поэтому на нём тренируют команды и отлаживают скрипты. Команда возвращает ненулевой код при неудаче, и на этом строят простые проверки в системах наблюдения: скрипт раз в сутки делает контрольный прогон и молчит, пока код возврата равен нулю.

Отдельно о соблазне выпустить сертификат заново немедленно. Флаг --force-renewal существует, но каждый принудительный заказ тратит недельные лимиты. Веские причины для него - компрометация закрытого ключа или срочная замена после ошибки; в остальном честное окно продления надёжнее и тише.

Частые ошибки при выпуске и продлении и способы их лечения

Почти каждая неудача оставляет запись в /var/log/letsencrypt/letsencrypt.log, и центр сертификации в ответах называет причину своими словами. Порядок диагностики при неудачном продлении сводится к четырём шагам:

  1. прочитать последние строки журнала /var/log/letsencrypt/letsencrypt.log, где причина названа прямо;
  2. проверить снаружи доступность проверочного пути по 80 порту и отсутствие лишнего редиректа;
  3. запустить контрольный прогон продления и сравнить сообщение об ошибке с журналом;
  4. взглянуть на текст ошибки и убедиться, что дело не в недельных лимитах выпусков.

Ошибка про недостаточную авторизацию ("The client lacks sufficient authorization") означает, что центр не смог забрать проверочный файл. Виноваты закрытый 80 порт в облачной панели безопасности, редирект всего трафика на https до прохождения проверки или неверный корень в webroot. Быстрая проверка - положить текстовый файл и забрать его снаружи:

echo check | sudo tee /var/www/example/.well-known/acme-challenge/probe.txt
curl -I http://example.com/.well-known/acme-challenge/probe.txt

Ответ 200 означает, что путь проходим. Ошибка "Timeout during connect" почти всегда про фаервол: пакеты до 80 порта не доходят. Сообщение "Could not bind to port 80" подсказывает, что standalone запустили при работающем nginx; здесь выручат --nginx или webroot.

Семья лимитных ошибок опознаётся по фразе "too many certificates already issued". На точный набор имён центр выдаёт не больше пяти сертификатов за семь дней, на весь зарегистрированный домен - не больше пятидесяти за неделю, а учётная запись может создать до трёхсот заказов за три часа. Лечение одно: ожидание, тестовый центр или продление через ARI, которое лимитов не расходует.

Ошибка DNS выглядит как NXDOMAIN или SERVFAIL при поиске проверочного имени: зона ещё не обновилась после переноса, делегирование съехало, в записи опечатка. Наконец, загадка со свежими файлами и старым сертификатом в браузере решается просто: это не Certbot, это nginx без перечитывания конфигурации, то есть пропущенный deploy-хук из предыдущего раздела.

Контроль срока действия сертификата и уведомления об истечении

Автоматизация не отменяет контроля, она меняет его частоту: раз в квартал вместо утренней паники. Команда certbot certificates выведет список с датами окончания:

sudo certbot certificates

Срок по живому серверу проверяет openssl прямо из ответа:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate

Проверку делают с внешней машины: изнутри запрос может пройти мимо сервера через кеширующий промежуточный узел и показать чужой сертификат. Письма об истечении срока Let's Encrypt шлёт на адрес учётной записи, указанный при первом запуске. Смена адреса - одна команда:

sudo certbot update_account -m Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.

Дублирующий слой наблюдения с внешней точки тоже полезен: проверки TLS умеют почти все сервисы мониторинга, и письмо придёт, даже если сервер целиком молчит. Ручной труд с напоминаниями в календаре проигрывает таймеру всегда, потому что таймер не ходит в отпуск.

Хорошее продление скучное. О нём не вспоминают ни в отпуске, ни в самую горячую рабочую неделю, о нём узнают только по строчке в журнале. Настроенный один раз Certbot с таймером, deploy-хуком и ежеквартальным контрольным прогоном превращает 90-дневные сертификаты из повода для красной страницы в часть инфраструктуры, которая просто работает. Десять минут внимания в квартал - честная цена за спокойный сон и выходные без экстренных выпусков.