Wildcard-сертификат закрывает все поддомены одного уровня: *.example.com защищает api.example.com, shop.example.com и любой хост, который вы создадите позже. Один выпуск заменяет десятки отдельных сертификатов и снимает боль с новыми поддоменами.
Для шаблонных имён центры сертификации принимают единственный тип проверки, DNS-01. Контроль над зоной подтверждается TXT-записью _acme-challenge.example.com. HTTP-01 и TLS-ALPN-01 проверяют конкретный хост и для wildcard не подходят.
Дальше идут рабочие команды для certbot и acme.sh, разбор минимальных прав API-токена, настройка hooks и renew, диагностика ошибок валидации. Примеры проверены на Ubuntu 24.04 с Let's Encrypt в роли центра сертификации.
Что такое DNS-01 challenge и зачем он нужен для wildcard-сертификатов
DNS-01 это один из типов ACME-валидации. Клиент получает от CA ключ авторизации, вычисляет из него значение и публикует в TXT-записи _acme-challenge.ваш-домен. Центр сертификации опрашивает DNS зоны и сверяет содержимое записи с ожидаемым. Совпадение подтверждает контроль над доменом, после чего выпускается сертификат.
Сертификаты Let's Encrypt действуют 90 дней. Лимиты: 50 сертификатов на зарегистрированный домен в неделю и 5 дубликатов с одинаковым набором имён. Шаблон *.example.com не покрывает apex, поэтому в заявке всегда указывайте оба имени: -d example.com -d '*.example.com'.
Почему HTTP-01 не подходит для wildcard
Валидация HTTP-01 требует, чтобы CA открыла адрес вида http://ваш-домен/.well-known/acme-challenge/токен и получила файл с нужным содержимым. У шаблона *.example.com нет конкретного хоста. Разложить файл по всем возможным поддоменам нельзя, включая те, что появятся позже.
Правила Let's Encrypt разрешают wildcard только при валидации dns-01. Дополнительный плюс: проверка не требует открытого 80 порта и работает за NAT, балансировщиком, CDN. Нужен только API-доступ к управлению DNS-записями зоны.
Как работает валидация через TXT-запись
- Клиент регистрирует ACME-аккаунт и отправляет заявку на нужные имена.
- CA возвращает список авторизаций, для каждой выбирается challenge типа dns-01.
- Клиент считает SHA-256 от ключа авторизации и кодирует результат в base64url. Это и есть значение TXT-записи.
- Через API DNS-провайдера создаётся TXT-запись _acme-challenge.example.com с TTL 60.
- Клиент сообщает CA о готовности. CA опрашивает авторитативные серверы зоны, а иногда и публичные резолверы.
- После успешной проверки сертификат выдаётся, а TXT-запись удаляется.
Между созданием записи и опросом проходит время: значение должно разойтись по всем NS зоны, кэши резолверов должны его увидеть. Это и есть propagation delay, на нём чаще всего спотыкается автоматизация.
Настройка DNS-01 challenge: пошаговая инструкция для certbot и acme.sh
certbot ставится из пакетов дистрибутива вместе с плагином провайдера. Один плагин на каждого DNS-провайдера, ставить все сразу не нужно.
sudo apt update sudo apt install certbot python3-certbot-dns-cloudflare
acme.sh зависимостей от Python не имеет. Установите его удобным для вас способом из репозитория проекта, затем проверьте версию командой acme.sh --version. Дальше выпуск идёт от имени обычного пользователя, файлы лежат в ~/.acme.sh.
Пример для Cloudflare: токен и команда
Создайте API Token по шаблону Edit zone DNS: Permissions Zone / DNS / Edit, плюс Zone / Zone / Read. В разделе Zone Resources выберите Include, Specific zone и укажите example.com. Global API Key не используйте, он даёт полный доступ к аккаунту.
Файл /etc/letsencrypt/cloudflare.ini:
dns_cloudflare_api_token = ваш_токен
Закройте файл от посторонних и запустите выпуск.
sudo chmod 600 /etc/letsencrypt/cloudflare.ini sudo chown root:root /etc/letsencrypt/cloudflare.ini sudo certbot certonly --dns-cloudflare \ --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \ --dns-cloudflare-propagation-seconds 30 \ -d example.com -d '*.example.com'
Проверка результата:
sudo certbot certificates sudo ls -la /etc/letsencrypt/live/example.com/
В каталоге появятся fullchain.pem, privkey.pem и chain.pem. Срок действия указан в выводе команды certbot certificates.
Пример для AWS Route53
sudo apt install python3-certbot-dns-route53
Плагин берёт ключи из стандартной цепочки AWS: переменные окружения AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY либо файл ~/.aws/credentials. Пользователю или роли нужна политика:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["route53:ListHostedZones", "route53:GetChange"],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["route53:ChangeResourceRecordSets"],
"Resource": "arn:aws:route53:::hostedzone/Z0123456789ABC"
}
]
}
sudo certbot certonly --dns-route53 -d example.com -d '*.example.com'
Проверка: sudo certbot renew --dry-run. Если политика урезана неверно, в логе появится AccessDenied с названием запрещённого действия.
Пример для DigitalOcean
export DO_API_KEY="dop_v1_ваш_токен" acme.sh --issue --dns dns_dgon -d example.com -d '*.example.com' --dnssleep 30
Вариант для certbot: пакет python3-certbot-dns-digitalocean и строка dns_digitalocean_token в отдельном ini-файле с правами 600. У DigitalOcean токены не привязаны к конкретной зоне, об этом ниже.
Сводка по параметрам для типовых провайдеров:
| Провайдер | Плагин | Где хранится токен | Ключевой флаг |
|---|---|---|---|
| Cloudflare | certbot-dns-cloudflare | /etc/letsencrypt/cloudflare.ini | --dns-cloudflare |
| AWS Route53 | certbot-dns-route53 | ~/.aws/credentials | --dns-route53 |
| DigitalOcean | certbot-dns-digitalocean | /etc/letsencrypt/digitalocean.ini | --dns-digitalocean |
| Любой с API | acme.sh dnsapi | переменные окружения CF_Token, DO_API_KEY | --dns dns_cf |
Базовые сценарии с HTTP-01, установкой сертификата и обновлением разобраны отдельно: Полная настройка Let's Encrypt: бесплатные SSL-сертификаты и автоматическое обновление, включая кейс с ошибкой DNS-01 на Cloudflare.
Минимальные права API-токена: принцип наименьших привилегий
Токену для ACME нужны две операции: создать TXT-запись _acme-challenge и удалить её. Чтение и правка A, MX, CNAME не требуются. Если токен утечёт, злоумышленник с правом менять любые записи зоны перенаправит трафик, поэтому доступ ограничивают одной зоной и одним типом записей.
Таблица минимальных прав для популярных DNS-провайдеров
| Провайдер | Тип доступа | Минимальные права | Ограничение зоны |
|---|---|---|---|
| Cloudflare | API Token | Zone:DNS:Edit, Zone:Zone:Read | Zone Resources: Include, Specific zone |
| AWS Route53 | IAM user или role | route53:ChangeResourceRecordSets, route53:GetChange, route53:ListHostedZones | Resource ARN конкретной hosted zone |
| DigitalOcean | Personal Access Token | read/write для DNS | На уровне аккаунта, ограничения зоны нет |
Cloudflare дополнительно позволяет привязать токен к списку IP. Если сервер с certbot имеет статический адрес, добавьте его в Client IP Address Filtering: украденный токен с чужого адреса не сработает.
Как проверить, что токен не имеет лишних прав
Начните с проверки самого токена. Для Cloudflare:
curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer $CF_TOKEN"
В ответе должно быть success: true и status: active. Дальше проверьте границы: попробуйте удалить тестовую запись в зоне, которой нет в Zone Resources. Ожидаемый результат: ошибка 403 с кодом 9109. Если запись удалилась, токен выдан слишком широко.
Для Route53 попытка изменить зону вне ARN даёт AccessDenied:
aws route53 change-resource-record-sets --hosted-zone-id Z999 \ --change-batch file://test.json
Отдельно убедитесь, что у токена нет прав на другие сервисы провайдера. Для этого достаточно аудита в панели: список разрешений должен содержать только DNS-операции.
Автоматизация: hooks, renew и перезагрузка сервисов
Сертификат живёт 90 дней, продление запускается автоматически. В certbot из коробки работает systemd timer certbot.timer: он стартует дважды в сутки со случайной задержкой. Проверить состояние просто.
systemctl list-timers | grep certbot systemctl status certbot.timer sudo certbot renew --dry-run
Флаг --dry-run прогоняет весь цикл на staging-окружении CA, реальный сертификат не трогается. Запускайте его после каждой правки конфигурации, это дешёвая страховка от блокировки на неделю из-за rate limit. Сценарии продления удобно обкатывать на отдельном VPS: Timeweb Cloud даёт сервер и снапшоты, откат к рабочему состоянию занимает минуты.
Deploy-hook: перезагрузка Nginx и других сервисов
Обновлённый сертификат на диске не значит, что сервис его подхватил. Для этого есть deploy-hook, который выполняется только при фактическом продлении.
sudo certbot renew --deploy-hook "systemctl reload nginx"
Для постоянной настройки положите скрипт в каталог хуков. Имя начинается с цифр, они задают порядок запуска.
#!/bin/sh systemctl reload nginx systemctl reload postfix systemctl reload dovecot
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/20-reload-services.sh
Каталоги /etc/letsencrypt/renewal-hooks/pre и post выполняют команды до и после попытки продления. Хук pre удобен для проверки доступности DNS API, хук post для уведомления в мониторинг.
В acme.sh аналог deploy-hook называется reloadcmd. Команда сохраняется вместе с сертификатом через --install-cert.
acme.sh --install-cert -d example.com \ --key-file /etc/nginx/ssl/example.key \ --fullchain-file /etc/nginx/ssl/example.crt \ --reloadcmd "systemctl reload nginx"
Дополнительно доступны --pre-hook, --post-hook и --renew-hook. Первые два срабатывают всегда, третий только при успешном продлении.
Systemd timer vs cron для renew
certbot ставит timer вместе с пакетом. Логи уходят в journald, задержка рандомизирована, пропущенный запуск повторяется после включения сервера. Cron-файл /etc/cron.d/certbot нужен только в нестандартных сборках.
acme.sh создаёт собственное задание при установке. Проверить его можно так:
crontab -l | grep acme.sh journalctl -u certbot.service -n 50 --no-pager
Для парка серверов выгодно выпускать сертификат на одной машине и раздавать по SSH или через API. Автоматическое продление в Nginx с проверкой через dry-run и перезагрузкой без простоя разбирает статья Автоматическое обновление SSL-сертификатов Let's Encrypt в Nginx с Certbot. Готовая конфигурация TLS 1.3 и скрипт обновления через systemd timer есть в материале Nginx HTTPS в 2026 году: TLS 1.3, Let's Encrypt и безопасная конфигурация.
Propagation delay: почему валидация может занять время
После вызова API DNS-провайдера запись появляется не мгновенно. Авторитативные серверы зоны обновляются почти сразу, но резолверы держат кэш по TTL, а CA опрашивает зону с нескольких точек. Пока хотя бы один запрос не увидит TXT-запись, валидация не пройдёт.
Типичные задержки: Cloudflare 5-30 секунд, Route53 10-60 секунд, медленные панели и провайдеры с несколькими NS дают паузу до 5 минут. Если зона обслуживается четырьмя серверами, значение должно совпасть у всех четырёх.
Как измерить propagation delay вручную
dig NS example.com +short dig +short TXT _acme-challenge.example.com @1.1.1.1 dig +short TXT _acme-challenge.example.com @8.8.8.8 dig +short TXT _acme-challenge.example.com @ns1.provider.net
Сначала получите список авторитативных NS командой dig NS, затем запросите каждый сервер напрямую. Расхождение ответов означает, что запись разошлась не везде. Значение TXT меняется при каждом выпуске, поэтому старую запись с прошлым хэшем нужно удалять: две TXT с одним именем собьют проверку.
Настройка таймаута в certbot и acme.sh
Плагины certbot принимают флаг propagation-seconds, по умолчанию 10 секунд. Увеличивайте его по факту замеров.
--dns-cloudflare-propagation-seconds 60 --dns-route53-propagation-seconds 60
В acme.sh за паузу отвечает параметр --dnssleep, значение в секундах.
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com' --dnssleep 120
Многие dnsapi-скрипты acme.sh сами проверяют запись через dns_zone_check и вместо фиксированной паузы ждут подтверждения от API провайдера. Признак работы это строка Zone_Check: OK в debug-выводе. Фиксированный таймаут в 300 секунд для всех провайдеров замедляет продление, зато не ломает его: держите его как временную страховку.
Диагностика ошибок certbot и acme.sh
Порядок разбора любой неудачи: найдите точный текст ошибки в логе, проверьте TXT-запись вручную через dig, сверьте значение с тем, что сгенерировал клиент.
Таблица типичных ошибок и решений
| Ошибка | Причина | Решение |
|---|---|---|
| NXDOMAIN looking up TXT for _acme-challenge.example.com | Запись создана в другой зоне или не успела появиться | Проверить зону через dig NS, добавить TXT вручную, поднять таймаут |
| Incorrect TXT record ... found at _acme-challenge.example.com | Осталась старая TXT-запись, проверка видит чужой хэш | Удалить лишние TXT с этим именем, повторить выпуск |
| Timeout during connect | Резолвер CA не получил ответ по записи | Проверить через @1.1.1.1 и @8.8.8.8, увеличить propagation-seconds |
| Unauthorized, 403 от API провайдера | Токен без нужных прав или выдан для другой зоны | Пересоздать токен с Zone:DNS:Edit, проверить /user/tokens/verify |
| DNS problem: SERVFAIL looking up CAA | Проблемы DNSSEC на зоне или CAA-запись запрещает выбранный CA | Проверить dig CAA example.com и состояние DNSSEC |
| rateLimited | Исчерпаны лимиты: 50 сертификатов на домен в неделю, 5 дубликатов | Отладить на staging CA через --dry-run, дождаться окна |
Проверка TXT-записи вручную
Значение записи видно в выводе клиента. certbot с флагом -v печатает challenge, а режим --debug-challenges останавливается перед валидацией и ждёт нажатия Enter, оставляя запись в DNS.
sudo certbot certonly --manual --preferred-challenges dns \ -d example.com -d '*.example.com' --debug-challenges -v
Для acme.sh включите подробный вывод и сравните хэш из лога с ответом DNS.
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com' --debug 2 dig +short TXT _acme-challenge.example.com @1.1.1.1
Где смотреть логи certbot и acme.sh
certbot пишет всё в /var/log/letsencrypt/letsencrypt.log. Для подробностей добавьте -v или -vvv, файл ротируется автоматически. Последние события по конкретному домену:
sudo grep -i "example.com" /var/log/letsencrypt/letsencrypt.log | tail -n 40 journalctl -u certbot.service --since "1 hour ago"
acme.sh ведёт лог в ~/.acme.sh/acme.sh.log, а ключ --log позволяет указать свой путь. Строки с ошибкой API провайдера ищите по кодам HTTP и слову error.
Безопасность хранения и ротации API-токенов
Файл с токеном это фактически ключ к зоне. Компромисс токена с правами на DNS позволяет выпустить сертификат на ваш домен и провести MITM-атаку через подмену записей. Доступ к файлу закрывают, а сам токен меняют по расписанию.
Права на файлы и владелец
sudo chown root:root /etc/letsencrypt/cloudflare.ini sudo chmod 600 /etc/letsencrypt/cloudflare.ini sudo ls -la /etc/letsencrypt/cloudflare.ini
Ожидаемый вывод: -rw------- 1 root root. Токены не попадают в git: добавьте *.ini и файлы credentials в .gitignore, а в Ansible, Terraform и CI передавайте их через переменные окружения или vault, не через репозиторий.
Интеграция с HashiCorp Vault
Vault хранит токен централизованно и позволяет отозвать его без правки конфигов на серверах. Агент подставляет значение в файл по шаблону.
{{ with secret "secret/cloudflare/dns" }}{{ .Data.data.api_token }}{{ end }}
Секрет кладётся командой vault kv put secret/cloudflare/dns api_token=... Альтернатива для одиночного сервера: systemd credentials и директива LoadCredential, тогда токен живёт в памяти и не остаётся на диске. Ротацию привяжите к сроку сертификата и меняйте токен раз в 90 дней. После смены проверьте выпуск командой certbot renew --dry-run.
Защиту самого веб-сервера после выпуска сертификата разбирает руководство Nginx и Apache: полная защита веб-серверов (HTTPS, заголовки, WAF и противодействие атакам): там собраны HSTS, CSP и mod_security на уровне конфигурации.
Сравнение certbot и acme.sh для DNS-01
Оба клиента умеют всё нужное для wildcard через DNS-01. Выбор зависит от окружения и числа провайдеров, с которыми вы работаете.
| Критерий | certbot | acme.sh |
|---|---|---|
| Язык и зависимости | Python 3, ставится пакетом дистрибутива | Shell, нужны только openssl и curl |
| Поддержка DNS-провайдеров | Отдельный python-плагин на провайдера | Больше 150 встроенных dnsapi-скриптов |
| Автозапуск продления | systemd timer или cron из пакета | Собственное задание cron при установке |
| Хуки | Каталоги renewal-hooks, флаг --deploy-hook | --pre-hook, --post-hook, --renew-hook, --reloadcmd |
| Расположение файлов | /etc/letsencrypt, обычно от root | ~/.acme.sh, обычно от обычного пользователя |
certbot удобнее там, где серверы управляются пакетами и Ansible: путь /etc/letsencrypt/live знаком большинству конфигураций Nginx и Postfix, а systemd timer виден в journald. acme.sh выигрывает на роутерах, в контейнерах без Python и при работе с десятком разных DNS-провайдеров.
Минимальный рабочий набор для вашей зоны: токен только с правом правки DNS в одной зоне, файл credentials с правами 600, propagation-seconds по результатам замеров, deploy-hook на перезагрузку сервисов и запуск certbot renew --dry-run после каждой правки конфигурации. Этого достаточно, чтобы продление wildcard-сертификата шло без ручного вмешательства.