Wildcard-сертификаты через ACME DNS-01: автоматизация, безопасность токенов и диагностика | AdminWiki

Wildcard-сертификаты через ACME DNS-01: автоматизация, безопасность токенов и диагностика

11 сентября 2026 11 мин. чтения
Содержание статьи

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-запись

  1. Клиент регистрирует ACME-аккаунт и отправляет заявку на нужные имена.
  2. CA возвращает список авторизаций, для каждой выбирается challenge типа dns-01.
  3. Клиент считает SHA-256 от ключа авторизации и кодирует результат в base64url. Это и есть значение TXT-записи.
  4. Через API DNS-провайдера создаётся TXT-запись _acme-challenge.example.com с TTL 60.
  5. Клиент сообщает CA о готовности. CA опрашивает авторитативные серверы зоны, а иногда и публичные резолверы.
  6. После успешной проверки сертификат выдаётся, а 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 токены не привязаны к конкретной зоне, об этом ниже.

Сводка по параметрам для типовых провайдеров:

ПровайдерПлагинГде хранится токенКлючевой флаг
Cloudflarecertbot-dns-cloudflare/etc/letsencrypt/cloudflare.ini--dns-cloudflare
AWS Route53certbot-dns-route53~/.aws/credentials--dns-route53
DigitalOceancertbot-dns-digitalocean/etc/letsencrypt/digitalocean.ini--dns-digitalocean
Любой с APIacme.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-провайдеров

ПровайдерТип доступаМинимальные праваОграничение зоны
CloudflareAPI TokenZone:DNS:Edit, Zone:Zone:ReadZone Resources: Include, Specific zone
AWS Route53IAM user или roleroute53:ChangeResourceRecordSets, route53:GetChange, route53:ListHostedZonesResource ARN конкретной hosted zone
DigitalOceanPersonal Access Tokenread/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. Выбор зависит от окружения и числа провайдеров, с которыми вы работаете.

Критерийcertbotacme.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-сертификата шло без ручного вмешательства.

Поделиться:
Сохранить гайд? В закладки браузера