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 дней |
Проверить в любой момент:
Как автообновление узнаёт что пора обновлять: каждый запуск 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
Проверить статус:
Пример вывода:
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
Условие -d /run/systemd/system — cron не запускается, если система под systemd (Ubuntu 22.04 — под systemd). Значит cron сам себя отключает. Работает только certbot.timer.
Проверка что автообновление работает¶
Логи за последние N дней¶
Что искать:
- Starting certbot.service... — запуск
- Deactivated successfully — завершилось без ошибок
- Между ними < 1 секунды — значит quiet-запуск, ничего не обновляли (все серты свежие)
- Если между ними много строк — было реальное обновление
Dry-run обновления (не трогая реальные серты)¶
Это делает тестовый прогон через staging Let's Encrypt — без записи новых сертификатов, но с полной проверкой процесса (nginx-challenge, DNS, конфиг). Обязательно запустить хотя бы раз после установки.
Форсированное обновление (для теста в production)¶
⚠ Осторожно: 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
Команда:
Что происходит:
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
Проверить:
Только выпуск (без изменения nginx)¶
Полезно если хотите сами прописать пути в nginx (например, для нестандартных конфигураций как listen 8443).
Wildcard (*.example.com) — только DNS challenge¶
Для wildcard нужен DNS-01 challenge (LE не может проверить wildcard через HTTP):
Certbot попросит вручную создать TXT-запись _acme-challenge.example.com со значением из вывода. Проверить перед подтверждением:
⚠ Wildcard-сертификаты не автообновляются без DNS-плагина (напр., certbot-dns-cloudflare, certbot-dns-yandex).
Расширить существующий сертификат (добавить SAN)¶
Пример — как мы добавили www.alatyr-taverna.ru 05.08.2026:
Важно --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 или не распространилась.
Проверить:
Решение: подождать 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 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
Пример вывода:
Онлайн-мониторы (для внешней подстраховки):
- sslmate cert monitoring — почтовые уведомления за 30/14/7 дней до expiry (бесплатно)
- https://checktls.com/TestReceiver — комплексная проверка
- https://www.ssllabs.com/ssltest/ — рейтинг конфигурации (у нас должно быть A+)
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
Ссылки¶
- Официальная документация: certbot.eff.org
- Let's Encrypt rate limits: letsencrypt.org/docs/rate-limits
- Best practices: ssl-config.mozilla.org
История изменений¶
| Дата | Что |
|---|---|
| 2026-08-05 | Первая версия. Задокументированы 3 сертификата, systemd timer автообновления, renewal-конфиги, /etc/letsencrypt/ структура, типичные ошибки. Найдены пробелы: нет внешнего мониторинга, /etc/letsencrypt не в бэкапе, dry-run никогда не тестировали. |