Перейти к содержанию

Certbot: Let's Encrypt на VPS

Автоматический выпуск и обновление TLS-сертификатов через certbot + nginx-plugin.

Дата снимка: 05.08.2026 Хост: VPS vm-mini (46.17.99.183) Версия certbot: 2.9.0 ACME сервер: Let's Encrypt production Account ID: f5105d05a929230286c6f09b9ba011fc Key type: ECDSA (короче и быстрее RSA)


Быстрая справка

Параметр Значение
Развёртывание apt-пакет certbot + python3-certbot-nginx
Автообновление systemd timer (certbot.timer) — 2 раза/сутки
Порог обновления 30 дней до expiry
Аутентификатор nginx (challenge через веб-сервер)
Инсталлер nginx (автоматически правит sites-enabled)
Директория конфигов LE /etc/letsencrypt/
Логи journalctl -u certbot.service + /var/log/letsencrypt/
Активных сертификатов 3

Активные сертификаты (05.08.2026)

Certificate Name Domains (SAN) Expiry Осталось
alatyr-service.ru alatyr-service.ru, www.alatyr-service.ru 2026-10-16 72 дня
alatyr-taverna.ru alatyr-taverna.ru, www.alatyr-taverna.ru 2026-10-23 78 дней
vault.alatyr-service.ru vault.alatyr-service.ru 2026-10-22 78 дней

Проверить в любой момент:

sudo certbot certificates

Как автообновление узнаёт что пора обновлять: каждый запуск certbot renew проверяет expiry всех сертификатов; если <30 дней — обновляет, иначе пропускает.


Как работает автообновление

Timer: certbot.timer

Файл: /usr/lib/systemd/system/certbot.timer

[Unit]
Description=Run certbot twice daily

[Timer]
OnCalendar=*-*-* 00,12:00:00      # каждый день в 00:00 и 12:00
RandomizedDelaySec=43200          # + до 12ч случайного разброса
Persistent=true                   # догоняет пропуски (напр. VPS был выключен)

[Install]
WantedBy=timers.target

Проверить статус:

sudo systemctl list-timers certbot.timer --all

Пример вывода:

NEXT                            LEFT LAST                        PASSED UNIT
Wed 2026-08-05 15:54:05 MSK 1h 43min Wed 2026-08-05 04:55:05 MSK 9h ago certbot.timer

Service: certbot.service

Файл: /usr/lib/systemd/system/certbot.service

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew
PrivateTmp=true

Что делает: - -q (quiet) — не логирует успешные проверки (только ошибки) - renew — обновляет все сертификаты чей срок <30 дней - --no-random-sleep-on-renew — не спит ещё раз (timer уже сделал разброс) - PrivateTmp=true — изолированный /tmp/

Устаревший cron (спит)

Файл: /etc/cron.d/certbot

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && ...

Условие -d /run/systemd/system — cron не запускается, если система под systemd (Ubuntu 22.04 — под systemd). Значит cron сам себя отключает. Работает только certbot.timer.


Проверка что автообновление работает

Логи за последние N дней

sudo journalctl -u certbot.service --since "7 days ago" --no-pager

Что искать: - Starting certbot.service... — запуск - Deactivated successfully — завершилось без ошибок - Между ними < 1 секунды — значит quiet-запуск, ничего не обновляли (все серты свежие) - Если между ними много строк — было реальное обновление

Dry-run обновления (не трогая реальные серты)

sudo certbot renew --dry-run

Это делает тестовый прогон через staging Let's Encrypt — без записи новых сертификатов, но с полной проверкой процесса (nginx-challenge, DNS, конфиг). Обязательно запустить хотя бы раз после установки.

Форсированное обновление (для теста в production)

sudo certbot renew --force-renewal --cert-name alatyr-service.ru

Осторожно: LE имеет rate limits — не больше 5 обновлений одного домена в неделю. Использовать только для отладки.


Renewal-конфиги (/etc/letsencrypt/renewal/*.conf)

Файл: /etc/letsencrypt/renewal/<domain>.conf — создаётся при первом выпуске сертификата.

Пример (alatyr-service.ru.conf):

# renew_before_expiry = 30 days
version = 2.9.0
archive_dir = /etc/letsencrypt/archive/alatyr-service.ru
cert = /etc/letsencrypt/live/alatyr-service.ru/cert.pem
privkey = /etc/letsencrypt/live/alatyr-service.ru/privkey.pem
chain = /etc/letsencrypt/live/alatyr-service.ru/chain.pem
fullchain = /etc/letsencrypt/live/alatyr-service.ru/fullchain.pem

[renewalparams]
account = f5105d05a929230286c6f09b9ba011fc
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory
key_type = ecdsa

Все три renewal-конфига идентичны по структуре — отличаются только именем/путями/доменом.


Файловая структура /etc/letsencrypt/

/etc/letsencrypt/
├── accounts/                      # аккаунты LE (у нас один, ID f5105d05a929...)
├── archive/                       # ВСЕ старые версии сертификатов (никогда не удаляйте!)
│   ├── alatyr-service.ru/
│   │   ├── cert1.pem, cert2.pem, ...    # версии cert
│   │   ├── chain1.pem, ...
│   │   ├── fullchain1.pem, ...
│   │   └── privkey1.pem, ...
│   ├── alatyr-taverna.ru/
│   └── vault.alatyr-service.ru/
├── live/                          # СИМЛИНКИ на актуальные файлы из archive/
│   ├── alatyr-service.ru/
│   │   ├── cert.pem -> ../../archive/.../certN.pem
│   │   ├── chain.pem -> ...
│   │   ├── fullchain.pem -> ...
│   │   └── privkey.pem -> ...
│   ├── alatyr-taverna.ru/
│   └── vault.alatyr-service.ru/
├── renewal/                       # renewal-конфиги для каждого сертификата
│   ├── alatyr-service.ru.conf
│   ├── alatyr-taverna.ru.conf
│   └── vault.alatyr-service.ru.conf
├── renewal-hooks/                 # скрипты hooks на события renewal
│   ├── pre/                       # перед выпуском (напр., остановить nginx)
│   ├── deploy/                    # после успешного выпуска (напр., перезагрузить сервисы)
│   └── post/                      # после всей операции
├── options-ssl-nginx.conf         # шаблон TLS-настроек LE для nginx (см. nginx-vps-configs.md)
└── ssl-dhparams.pem               # DH params для perfect forward secrecy

Важно: nginx-конфиги ссылаются на /etc/letsencrypt/live/<domain>/fullchain.pem и privkey.pem — это симлинки. При обновлении certbot просто меняет цель симлинка, nginx подхватывает после reload.


Как выпустить сертификат для нового домена

Через nginx-plugin (авто, стандартный путь)

Предусловие: 1. В /etc/nginx/sites-enabled/<domain> есть HTTP-блок с listen 80 и правильным server_name 2. sudo nginx -t проходит 3. DNS уже указывает на VPS 46.17.99.183

Команда:

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

Что происходит: 1. Certbot делает HTTP-01 challenge через nginx (обращается к http://example.com/.well-known/acme-challenge/<token>) 2. LE проверяет — возвращает сертификат 3. Certbot автоматически редактирует ваш nginx-конфиг: добавляет listen 443 ssl блок с правильными ssl_certificate путями 4. Спрашивает "принудительный HTTPS-redirect?" — выберите 2: Redirect 5. Reload nginx

Проверить:

sudo certbot certificates | grep -A 5 example.com
curl -sI https://example.com/ | head -3

Только выпуск (без изменения nginx)

sudo certbot certonly --nginx -d example.com

Полезно если хотите сами прописать пути в nginx (например, для нестандартных конфигураций как listen 8443).

Wildcard (*.example.com) — только DNS challenge

Для wildcard нужен DNS-01 challenge (LE не может проверить wildcard через HTTP):

sudo certbot certonly --manual --preferred-challenges dns \
  -d "*.example.com" -d example.com

Certbot попросит вручную создать TXT-запись _acme-challenge.example.com со значением из вывода. Проверить перед подтверждением:

dig +short TXT _acme-challenge.example.com @1.1.1.1

⚠ Wildcard-сертификаты не автообновляются без DNS-плагина (напр., certbot-dns-cloudflare, certbot-dns-yandex).


Расширить существующий сертификат (добавить SAN)

Пример — как мы добавили www.alatyr-taverna.ru 05.08.2026:

sudo certbot --nginx -d alatyr-taverna.ru -d www.alatyr-taverna.ru --expand

Важно --expand — иначе certbot спросит "заменить или сохранить старый".

Проверить SAN:

sudo certbot certificates | grep -A 2 alatyr-taverna
# Domains: alatyr-taverna.ru www.alatyr-taverna.ru  ← оба должны быть

Отзыв сертификата

Если приватный ключ утёк или сертификат больше не нужен:

sudo certbot revoke --cert-path /etc/letsencrypt/live/<domain>/cert.pem
sudo certbot delete --cert-name <domain>

Порядок важен: сначала revoke (уведомляет LE), потом delete (удаляет локальные файлы).


Типичные ошибки и решения

1. Domain: X. Type: dns. Detail: DNS problem: NXDOMAIN

Причина: DNS-запись не указывает на VPS или не распространилась.

Проверить:

dig +short A example.com
# должен вернуть 46.17.99.183

Решение: подождать 5-30 минут после изменения DNS в reg.ru; проверять командой выше.

2. Timeout during connect (challenge fails)

Причина: LE не может дойти до вашего HTTP-сервера. Обычно: - Файрвол блокирует 80/tcp (проверить sudo ufw status) - nginx не слушает на 0.0.0.0:80 (может слушать только 127.0.0.1) - Провайдер блокирует внешний доступ на 80

Проверить снаружи:

# С внешней машины
curl -sI http://example.com/.well-known/acme-challenge/testtoken
# должен вернуть 404 (это ОК — путь не существует, но сервер отвечает)

3. You have an existing certificate that has exactly the same domains

Значит certbot нашёл существующий сертификат с теми же доменами. Опции: - 1: Reinstall — переустановить в конфиг (полезно если nginx-конфиг сломан) - 2: Renew — принудительно обновить (расходует rate limit LE)

Если это не то что надо — отменить (c + Enter) и проверить через sudo certbot certificates.

4. Rate limit exceeded

LE лимиты: - 5 обновлений одного набора доменов в неделю - 50 сертификатов на домен в неделю - 300 pending authorizations в час на аккаунт

Решение: ждать 7 дней. Использовать --dry-run для отладки без расходования лимита.

5. Сертификат обновился, но nginx отдаёт старый

Причина: nginx не перезагружен после обновления.

Решение: проверить renewal-hook:

sudo ls /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

Note: для nginx-plugin certbot обычно сам дёргает nginx reload — но лишний hook не помешает.


Мониторинг сроков (external check)

Помимо certbot certificates, полезно проверять снаружи:

# openssl — какая дата expiry
echo | openssl s_client -connect alatyr-service.ru:443 -servername alatyr-service.ru 2>/dev/null \
  | openssl x509 -noout -dates

Пример вывода:

notBefore=Jul 18 17:53:22 2026 GMT
notAfter=Oct 16 17:53:21 2026 GMT

Онлайн-мониторы (для внешней подстраховки):

TODO: - [ ] Настроить cert monitoring через sslmate certspotter (или свой скрипт)


Резервное копирование Let's Encrypt

Что бэкапить:

  • /etc/letsencrypt/ — всё целиком (аккаунты, archive, live, renewal)
  • Особенно accounts/ — потерять аккаунт = потерять историю rate limits, потенциально проблемы при revoke старых сертов

Резервная копия (полный tarball):

sudo tar -czf /root/letsencrypt-backup-$(date +%F).tar.gz -C /etc letsencrypt
sudo gpg --symmetric --cipher-algo AES256 \
  --output /root/letsencrypt-backup-$(date +%F).tar.gz.gpg \
  /root/letsencrypt-backup-$(date +%F).tar.gz
sudo rm /root/letsencrypt-backup-$(date +%F).tar.gz

TODO: - [ ] Добавить /etc/letsencrypt/ в backup.sh (сейчас Vaultwarden бэкапит только свой data/)

Восстановление:

sudo gpg --decrypt letsencrypt-backup-2026-08-05.tar.gz.gpg > letsencrypt-backup.tar.gz
sudo tar -xzf letsencrypt-backup.tar.gz -C /etc/
sudo systemctl reload nginx

TODO (сводка)

🔴 Приоритет 1 — надёжность

  • Настроить сертификатный мониторинг через certspotter (email за 30/14/7 дней до expiry — как страховка если certbot сломается)
  • Добавить /etc/letsencrypt/ в backup — сейчас не бэкапится (потеря аккаунта = проблема при revoke)
  • Проверить работу через sudo certbot renew --dry-run (никогда не запускался; убедиться что процесс работает end-to-end)

🟡 Приоритет 2 — оптимизация

  • Добавить deploy-hook для reload nginx на всякий случай (сейчас работает через nginx-plugin, но hook — страховка)
  • Отключить legacy cron /etc/cron.d/certbot (он спит из-за systemd, но убрать чисто для порядка)

🟢 Приоритет 3 — будущее

  • Рассмотреть DNS-plugin для reg.ru (certbot-dns-...) для wildcard-сертификатов
  • Настроить CAA-запись в DNS (см. dns-alatyr-service.md) чтобы ограничить выпуск только Let's Encrypt

Ссылки


История изменений

Дата Что
2026-08-05 Первая версия. Задокументированы 3 сертификата, systemd timer автообновления, renewal-конфиги, /etc/letsencrypt/ структура, типичные ошибки. Найдены пробелы: нет внешнего мониторинга, /etc/letsencrypt не в бэкапе, dry-run никогда не тестировали.