lenec ru

← все посты

Let's Encrypt и certbot: автообновление сертификатов на VPS

11K

Let's Encrypt — бесплатный, рабочий, проверенный. Сертификаты выдаются на 90 дней, обновляются автоматически. И всё равно у меня раз в полгода кто-нибудь жалуется, что «сайт показывает истёкший сертификат». В девяти случаях из десяти — настройка обновления сделана криво. Расскажу, как сделать ровно один раз и забыть.

Контекст: Ubuntu 24.04 на VPS, nginx, certbot из дистрибутива (1.x). На Debian 12 и Astra Linux логика та же.

Установка

На свежем Ubuntu есть два варианта: пакет certbot с плагином python3-certbot-nginx или snap-версия. Я предпочитаю первый, потому что не люблю snap на серверах:

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

Версия в Ubuntu 24.04 — 2.x, она современная и поддерживает все нужные мне фичи: ECDSA-ключи, ACMEv2, профили, DNS-01.

Первый сертификат через nginx-плагин

Самый простой путь, если nginx уже стоит и слушает 80/443:

sudo certbot --nginx -d app.example.ru -d www.app.example.ru \
  --email admin@example.ru \
  --agree-tos --no-eff-email \
  --redirect

Что делает:

  • Запрашивает сертификат у Let's Encrypt.
  • Сам редактирует nginx-конфиг, добавляя ssl_certificate и редирект http→https.
  • Перезагружает nginx.

Для большинства типовых сценариев этого достаточно. Я однако предпочитаю руками держать nginx-конфиг и не давать certbot его трогать.

Webroot — мой выбор

Подход с webroot не трогает конфиг nginx и работает по принципу «certbot кладёт challenge-файл, nginx раздаёт его как статику».

В /etc/nginx/sites-available/myapp.conf в HTTP-блоке должен быть:

server {
    listen 80;
    listen [::]:80;
    server_name app.example.ru;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Создать каталог:

sudo mkdir -p /var/www/certbot
sudo chown -R www-data:www-data /var/www/certbot

Получить сертификат:

sudo certbot certonly --webroot \
  -w /var/www/certbot \
  -d app.example.ru -d www.app.example.ru \
  --email admin@example.ru \
  --agree-tos --no-eff-email \
  --key-type ecdsa

--key-type ecdsa — поведение Let's Encrypt по умолчанию даёт RSA. ECDSA-ключи короче, быстрее и поддерживаются всеми современными браузерами. На моих сервисах TLS-handshake с ECDSA измеримо быстрее.

После успешного запуска появляются файлы:

/etc/letsencrypt/live/app.example.ru/
  cert.pem
  chain.pem
  fullchain.pem
  privkey.pem

В nginx подключаем fullchain.pem как ssl_certificate и privkey.pem как ssl_certificate_key.

Автообновление

Тут начинается самое интересное и где чаще всего ломается.

В Ubuntu 22.04 и новее с пакетом certbot уже идёт systemd-таймер. Проверить:

systemctl list-timers | grep certbot

Должно быть что-то вроде certbot.timer с расписанием раз в 12 часов. Сам сервис certbot.service запускает certbot renew.

На Ubuntu 20.04 и более старых системах был cron-файл /etc/cron.d/certbot. Если у тебя смешанная инфраструктура, не лишне проверить, что какой-то один механизм работает.

Хук перезагрузки

certbot обновляет сертификат, но nginx по-прежнему держит в памяти старый. Нужно дать сигнал. Это делается через deploy-hook:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/bash
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Хук вызывается только если сертификат реально обновился. То есть таймер пинает certbot каждые 12 часов, но реальная работа (и reload nginx) случается за 30 дней до истечения.

Проверка

sudo certbot renew --dry-run

Эта команда честно проигрывает обновление, не выдавая нового сертификата. Если всё корректно настроено — увидишь «Congratulations, all renewals succeeded». Если нет — будет ошибка с конкретным доменом.

Дополнительно я смотрю, что таймер стоит и работает:

systemctl status certbot.timer
systemctl status certbot.service
journalctl -u certbot -n 50

Wildcard-сертификат через DNS-01

Если нужен *.example.ru, обычный HTTP-01 не подойдёт: Let's Encrypt требует доказать владение через DNS. Это означает заведение TXT-записи. Вариантов два:

  • Руками каждый раз через --preferred-challenges dns --manual. Не годится для авто-обновления.
  • DNS-плагин для твоего регистратора. Cloudflare, Yandex, AWS Route53 и пр.

Например, для Cloudflare:

sudo apt install python3-certbot-dns-cloudflare
sudo tee /etc/letsencrypt/cloudflare.ini >/dev/null <<EOF
dns_cloudflare_api_token = <your_token_with_zone_dns_edit>
EOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d 'example.ru' -d '*.example.ru' \
  --email admin@example.ru \
  --agree-tos --no-eff-email \
  --key-type ecdsa

API-токен делаешь с минимальными правами: только Zone:DNS:Edit на нужную зону. Не забывай chmod 600 на файл с токеном.

Для регистраторов, у которых нет certbot-плагина, есть универсальный путь — acme.sh: shell-клиент, поддерживающий десятки DNS-провайдеров. На Ubuntu я обычно беру certbot, на экзотике — acme.sh.

Несколько доменов в одном сертификате

Перечисляешь через -d. Можно держать до 100 имён в одном сертификате — формальное ограничение Let's Encrypt. На практике я предпочитаю один сертификат на один логический сервис: проще ротация, меньше радиус взрыва.

Пример: один сервис обслуживает app.example.ru и API на api.example.ru. Кладу в один сертификат — оба обновляются вместе.

Подводные камни

Открытый 80 порт

Для HTTP-01 challenge порт 80 должен быть доступен снаружи. Если ты закрыл его в файрволе «потому что используем только https» — обновление не пройдёт. Я держу 80 открытым для редиректа на 443 и для challenges.

Кеширующий CDN

Если перед сервером Cloudflare или Yandex Cloud CDN, и они кэшируют ответы на /.well-known/acme-challenge/... — challenge не дойдёт. Решение: либо использовать DNS-01, либо настроить bypass cache на этом пути.

Лимиты Let's Encrypt

На один домен — максимум 50 сертификатов в неделю, 5 дублирующих за 7 дней. На стейджинге легко вписаться при тестировании. Используй --staging для отладочных запросов: лимиты гораздо мягче, сертификаты — невалидные с точки зрения браузера, но процесс отрабатывается.

sudo certbot certonly --staging --webroot ...
# когда всё работает, делаешь то же самое без --staging

Истёк токен у DNS-провайдера

Незаметная проблема. Сертификат не обновляется, реал клиенты ничего не замечают, пока не подходит конец 90 дней. Я на каждом сервере держу cron, который раз в неделю проверяет дату истечения сертификата и алертит, если осталось меньше 25 дней:

expiry=$(echo | openssl s_client -servername app.example.ru -connect app.example.ru:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
left=$(( ( $(date -d "$expiry" +%s) - $(date +%s) ) / 86400 ))
[ $left -lt 25 ] && echo "cert expires in $left days" | mail -s alert me@example.ru

Удаление сертификата

Когда домен больше не нужен, certbot умеет почистить:

sudo certbot certificates                    # список
sudo certbot delete --cert-name app.example.ru

Удалить также записи в /etc/letsencrypt/renewal/*.conf, иначе certbot будет пытаться обновить уже не существующий сертификат и засорять журнал.

Шпаргалка

  • Используй webroot или DNS-01, не --nginx (лучше управлять конфигом руками).
  • Ключ ECDSA: короче, быстрее.
  • Deploy-hook на reload nginx.
  • Проверяй certbot renew --dry-run на свежем сервере.
  • Открывай 80 порт. CDN — bypass для acme-challenge или DNS-01.
  • Алерт на дату истечения как страховка.
  • На staging тренируйся через --staging.

Один раз настроенный certbot работает годами без вмешательства. Главное — не забывать deploy-hook и иметь страховочный алерт. Я с этим набором живу с 2018-го, и реальный сбой автообновления у меня случался ровно один раз — когда добавил CDN перед сайтом и не настроил bypass.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.