Автоматизация SSL для десятков доменов через acme.sh и DNS API (без Certbot)

Автоматизация SSL через acme.sh и DNS API: wildcard сертификаты Let's Encrypt, массовый выпуск, установка в Nginx. Без Certbot, без root, без боли

У вас 30 доменов. Или 50. Или сотня. Сертификаты нужно выпустить, настроить автообновление, развернуть на серверах — и желательно так, чтобы это работало само, без вашего участия в 3 ночи. Certbot справляется с одним-двумя доменами. Но когда доменов десятки, wildcard-сертификаты, разные DNS-провайдеры и всё это нужно воспроизводить в CI/CD — certbot начинает раздражать.

acme.sh решает эту задачу элегантно: чистый bash, нулевые зависимости, поддержка 150+ DNS API, wildcard из коробки. В этой статье — реальные команды, готовый bash-скрипт для массового выпуска и разбор типичных граблей.

После прочтения у вас будет: рабочий pipeline для автоматического выпуска и обновления SSL-сертификатов для любого количества доменов без ручного вмешательства.


Почему Certbot неудобен для массовых сертификатов

Certbot — это официальный клиент EFF для Let’s Encrypt. Он отлично справляется с задачей «поднять один сайт на одном сервере». Но стоит выйти за пределы этого сценария — начинаются проблемы.

Масштабирование — боль. Certbot хранит состояние в /etc/letsencrypt/ и плотно завязан на конкретную машину. Хотите воспроизвести конфигурацию на новом сервере? Будете копировать директории, прописывать renewal-конфиги вручную, следить за правами. Никакой декларативности.

HTTP-01 требует открытого порта 80. В Kubernetes, за балансировщиком, в сети без прямого доступа из интернета — certbot с webroot или standalone просто не заработает. Каждый новый домен требует настройки location-блока или отдельного ingress.

Wildcard — отдельная история. Certbot поддерживает wildcard через DNS-01 challenge, но поддержка DNS API у него значительно беднее. Для Cloudflare нужен плагин, для Route53 — другой, для большинства российских регистраторов — никакого. И все они требуют установки через pip, что в минималистичных окружениях превращается в зоопарк зависимостей.

Docker-friendly? Не особо. Официального легковесного образа нет, плагины тянут Python-зависимости, хранение состояния нетривиально.

Именно поэтому в инфраструктуре с десятками доменов всё чаще выбирают acme.sh + DNS challenge.


Что такое acme.sh

acme.sh — это реализация ACME-протокола в виде одного bash-скрипта. Никакого Python, никаких pip install, никаких системных зависимостей кроме curl и openssl — которые есть в любом Linux/macOS/BSD. Проект активно развивается с 2015 года и сегодня поддерживает более 150 DNS-провайдеров.

Ключевые преимущества для понимания почему это acme.sh ssl-автоматизация, а не очередная обёртка над certbot:

  • Один файл. Весь клиент — это bash-скрипт. Устанавливается за 10 секунд, работает без root.
  • DNS API из коробки. Cloudflare, AWS Route53, DigitalOcean, Gandi, Namecheap, reg.ru, и ещё 140+ провайдеров — всё включено, ничего доустанавливать не нужно.
  • Wildcard без плясок. DNS-01 challenge — нативный режим работы, не исключение.
  • Автообновление. При установке сам прописывает себя в cron. Сертификаты обновляются без вашего участия.
  • Несколько CA. Let’s Encrypt, ZeroSSL, BuyPass — переключаетесь одним флагом.

Репозиторий: github.com/acmesh-official/acme.sh


DNS Challenge: как это работает

Прежде чем переходить к командам — разберём механику. Это важно, потому что именно понимание letsencrypt dns challenge спасает от граблей с задержками DNS.

При HTTP-01 challenge Let’s Encrypt проверяет, лежит ли файл по определённому URL. При DNS-01 challenge всё иначе:

  1. Ваш ACME-клиент (acme.sh) обращается к Let’s Encrypt и говорит: «Хочу сертификат для example.com».
  2. Let’s Encrypt отвечает: «Добавь TXT-запись _acme-challenge.example.com со значением ХХХ_уникальный_токен_ХХХ».
  3. acme.sh через API вашего DNS-провайдера автоматически создаёт эту TXT-запись.
  4. acme.sh ждёт, пока запись распространится (TTL + задержки DNS).
  5. Let’s Encrypt делает DNS-запрос, находит TXT-запись, подтверждает владение доменом, выдаёт сертификат.
  6. acme.sh удаляет TXT-запись через API.

Весь этот процесс — автоматический. Вам не нужно трогать DNS вручную. Именно поэтому это работает для wildcard сертификатов letsencrypt (*.example.com) — HTTP-проверка не может подтвердить владение всеми поддоменами сразу, а DNS — может.

Важный нюанс про TTL: если у ваших DNS-записей высокий TTL (например, 3600 секунд), acme.sh будет долго ждать распространения. Для production рекомендую снизить TTL до 120–300 секунд заранее.


Установка acme.sh

Устанавливается без root, в домашнюю директорию пользователя:

curl https://get.acme.sh | sh -s email=admin@example.com

Что произошло после установки:

  • Скрипт скопирован в ~/.acme.sh/acme.sh
  • Создан alias acme.sh в ~/.bashrc или ~/.zshrc
  • Добавлен cron-job для автообновления

Перезагружаем shell или применяем изменения:

source ~/.bashrc

Устанавливаем Let’s Encrypt как CA по умолчанию (с 2021 года по умолчанию используется ZeroSSL — явно указываем LE):

acme.sh --set-default-ca --server letsencrypt

Проверяем установку:

acme.sh --version

💡 Если нужна установка для root (например, чтобы писать напрямую в /etc/nginx/ssl) — запускайте от root, тогда cron добавится в /etc/cron.d/.


Выпуск wildcard сертификата через DNS API

Разберём на примере Cloudflare — самого распространённого варианта. Это канонический пример работы acme.sh wildcard.

Шаг 1. Получаем API-токен Cloudflare

Идём в Cloudflare Dashboard → My Profile → API Tokens → Create Token. Нужны права: Zone / DNS / Edit для нужных зон. Записываем токен и Account ID.

Шаг 2. Экспортируем переменные

export CF_Token="ваш_api_token_cloudflare"
export CF_Account_ID="ваш_account_id"

acme.sh автоматически сохранит эти переменные в конфиге домена после первого выпуска — повторно указывать не нужно.

Шаг 3. Выпускаем wildcard сертификат

acme.sh --issue \
  --dns dns_cf \
  -d example.com \
  -d '*.example.com'

Флаги:

  • --dns dns_cf — использовать DNS API Cloudflare
  • -d example.com — основной домен
  • -d '*.example.com' — wildcard (одинарные кавычки обязательны!)

acme.sh создаст TXT-записи, подождёт распространения и выпустит сертификат. По умолчанию сертификаты хранятся в ~/.acme.sh/example.com/.

Для других DNS-провайдеров меняем только переменные и параметр --dns:

# AWS Route53
export AWS_ACCESS_KEY_ID="ключ"
export AWS_SECRET_ACCESS_KEY="секрет"
acme.sh --issue --dns dns_aws -d example.com -d '*.example.com'

# DigitalOcean
export DO_API_KEY="токен_digitalocean"
acme.sh --issue --dns dns_dgon -d example.com -d '*.example.com'

# Yandex Cloud DNS
export YC_Zone_ID="id_зоны"
export YC_SA_ID="id_сервисного_аккаунта"
export YC_SA_Key_File="путь_к_ключу.json"
acme.sh --issue --dns dns_yc -d example.com -d '*.example.com'

Поддержка DNS API: популярные провайдеры

acme.sh поддерживает более 150 DNS-провайдеров. Это и есть главная причина выбора acme.sh как инструмента для dns api letsencrypt-автоматизации. Вот актуальная шпаргалка по самым популярным:

Провайдер Параметр —dns Переменные окружения
Cloudflare dns_cf CF_Token, CF_Account_ID
AWS Route53 dns_aws AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
DigitalOcean dns_dgon DO_API_KEY
Google Cloud DNS dns_gcloud CLOUDSDK_CORE_PROJECT, GCLOUD_SERVICE_ACCOUNT_FILE
Yandex Cloud dns_yc YC_Zone_ID, YC_SA_ID, YC_SA_Key_File
reg.ru dns_regru REGRU_API_USERNAME, REGRU_API_PASSWORD
Namecheap dns_namecheap NAMECHEAP_USERNAME, NAMECHEAP_API_KEY
Hetzner dns_hetzner HETZNER_Token

Полный список с документацией: github.com/acmesh-official/acme.sh/wiki/dnsapi


Массовый выпуск SSL для десятков доменов

Вот где acme.sh раскрывается в полную силу. Это и есть настоящая автоматизация ssl сертификатов и выпуск ssl для нескольких доменов в промышленных масштабах.

Базовый скрипт для массового выпуска

#!/bin/bash

# Настройки DNS API (Cloudflare)
export CF_Token="ваш_токен"
export CF_Account_ID="ваш_account_id"

# Список доменов
domains=(
  "site1.com"
  "site2.com"
  "site3.com"
  "api.myproject.com"
  "admin.myproject.com"
)

# Директория для сертификатов
CERT_DIR="/etc/nginx/ssl"
mkdir -p "$CERT_DIR"

# Лог
LOG_FILE="/var/log/acme-issue.log"

echo "=== SSL Issue started: $(date) ===" >> "$LOG_FILE"

for domain in "${domains[@]}"; do
  echo "Processing: $domain" | tee -a "$LOG_FILE"

  # Выпускаем сертификат (wildcard + apex)
  acme.sh --issue \
    --dns dns_cf \
    -d "$domain" \
    -d "*.$domain" \
    --log "$LOG_FILE" 2>&1

  if [ $? -eq 0 ]; then
    # Устанавливаем сертификат в Nginx
    acme.sh --install-cert -d "$domain" \
      --key-file "$CERT_DIR/$domain.key" \
      --fullchain-file "$CERT_DIR/$domain.crt" \
      --reloadcmd "systemctl reload nginx"

    echo "✓ $domain — SUCCESS" | tee -a "$LOG_FILE"
  else
    echo "✗ $domain — FAILED (see log for details)" | tee -a "$LOG_FILE"
  fi

  # Пауза между запросами (Rate Limit protection)
  sleep 5
done

echo "=== Done: $(date) ===" >> "$LOG_FILE"

Что делает скрипт: проходит по списку доменов, для каждого выпускает wildcard-сертификат, устанавливает его в директорию Nginx, перезагружает Nginx только если сертификат успешно выпущен. Лог пишется в файл — удобно для аудита и отладки.

Вариант с доменами из файла

Если доменов много — удобнее хранить их в текстовом файле:

# domains.txt — один домен на строку
cat domains.txt | while read domain; do
  [ -z "$domain" ] && continue  # пропускаем пустые строки
  [ "${domain:0:1}" = "#" ] && continue  # пропускаем комментарии

  acme.sh --issue --dns dns_cf \
    -d "$domain" -d "*.$domain"
done

Параллельный выпуск (для больших инфраструктур)

# ОСТОРОЖНО: следите за Rate Limit Let's Encrypt
# Лимит: 50 сертификатов в час для одного аккаунта
cat domains.txt | xargs -P 5 -I {} \
  acme.sh --issue --dns dns_cf -d {} -d "*.{}"

⚠️ Rate Limit: Let’s Encrypt разрешает 50 сертификатов в час и 300 в неделю. При массовом выпуске сначала протестируйте на staging-сервере Let’s Encrypt.


Автоматическое обновление сертификатов

acme.sh при установке автоматически добавляет cron-задачу. Но стоит знать детали.

Проверка текущего cron

crontab -l | grep acme
# Должно быть что-то вроде:
# 0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

Ручной запуск обновления

# Проверка без реального обновления
acme.sh --cron --home ~/.acme.sh

# Принудительное обновление конкретного домена
acme.sh --renew -d example.com --force

Настройка cron с логированием

# Открываем crontab
crontab -e

# Добавляем (если не добавилось автоматически)
0 2 * * * ~/.acme.sh/acme.sh --cron --home ~/.acme.sh >> /var/log/acme-renew.log 2>&1

acme.sh обновляет сертификат за 30 дней до истечения. Если сертификат свежий — команда просто завершается без действий.

Уведомления об истечении

# Настройка email-уведомлений через SMTP
acme.sh --set-notify \
  --notify-hook <a class="wpil_keyword_link" title="mail" href="https://it-apteka.com/tag/mail/" target="_blank" rel="noopener" data-wpil-keyword-link="linked" data-wpil-monitor-id="877">mail</a> \
  --notify-level 2 \
  --notify-source example@gmail.com

Установка сертификатов в Nginx

Важный момент: acme.sh хранит сертификаты в ~/.acme.sh/. Не нужно напрямую указывать эти пути в Nginx — содержимое директории может меняться при обновлении. Используйте команду --install-cert, которая копирует файлы в нужное место и запускает hook при обновлении.

# Установка сертификата в Nginx
acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.crt \
  --reloadcmd "systemctl reload nginx"

После этого в конфиге Nginx:

server {
    listen 443 ssl http2;
    server_name example.com *.example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

    # остальная конфигурация
}

При каждом автоматическом обновлении acme.sh сам скопирует новые файлы и выполнит systemctl reload nginx. Ручного вмешательства не требуется.

Установка для нескольких доменов одной командой

#!/bin/bash
CERT_DIR="/etc/nginx/ssl"
domains=("site1.com" "site2.com" "site3.com")

for domain in "${domains[@]}"; do
  mkdir -p "$CERT_DIR/$domain"
  acme.sh --install-cert -d "$domain" \
    --key-file "$CERT_DIR/$domain/privkey.pem" \
    --fullchain-file "$CERT_DIR/$domain/fullchain.pem" \
    --reloadcmd "systemctl reload nginx"
done

Преимущества acme.sh перед Certbot

Параметр acme.sh Certbot
Зависимости curl, openssl (всегда есть) Python 3 + pip + плагины
Установка 1 строка, 5 секунд apt/snap + настройка
Root-права Не требуются Требуются для большинства операций
DNS API 150+ провайдеров встроено Ограничено, через внешние плагины
Wildcard сертификаты Нативно, легко Только через DNS-01, сложнее
Docker-friendly Да, легковесный образ Тяжеловат, нет официального slim-образа
Несколько CA Let’s Encrypt, ZeroSSL, BuyPass Преимущественно Let’s Encrypt
Хранение состояния ~/.acme.sh/ (переносимо) /etc/letsencrypt/ (системное)
Скорость обучения Выше (более логичный CLI) Средняя

Certbot по-прежнему хорош для простых случаев — особенно с плагином --nginx, который сам правит конфиги. Но для инфраструктурной автоматизации acme.sh выигрывает по всем параметрам.


Типичные ошибки при работе с acme.sh и DNS API

Ошибка 1: DNS API не работает — неправильные токены

# Проверяем, что переменные установлены
echo $CF_Token
echo $CF_Account_ID

# Тестируем API напрямую
curl -X GET "https://api.cloudflare.com/client/v4/zones" \
  -H "Authorization: Bearer $CF_Token" \
  -H "Content-Type: application/json"

Если API возвращает ошибку аутентификации — токен неверный или у него недостаточно прав. Токен Cloudflare должен иметь права Zone:DNS:Edit.

Ошибка 2: Таймаут при ожидании DNS-распространения

# Ошибка выглядит примерно так:
# Please add the following TXT record:
# Waiting for verification...
# Verify error: DNS problem: NXDOMAIN looking up TXT...

# Решение 1: увеличить время ожидания
acme.sh --issue --dns dns_cf -d example.com \
  --dnssleep 120

# Решение 2: проверить, что TXT-запись создалась
dig TXT _acme-challenge.example.com @8.8.8.8

Стандартное время ожидания у acme.sh — 20 секунд. Для провайдеров с медленным распространением DNS увеличьте через --dnssleep.

Ошибка 3: Rate Limit Let’s Encrypt

# Ошибка:
# Error: too many certificates (5) already issued for...

# Проверить статус rate limit:
# https://tools.letsdebug.net/

# Использовать staging для тестов:
acme.sh --issue --dns dns_cf \
  -d example.com -d '*.example.com' \
  --staging

# После успешного теста — выпуск боевого сертификата:
acme.sh --issue --dns dns_cf \
  -d example.com -d '*.example.com' \
  --force

Ошибка 4: Сертификат не обновляется автоматически

# Проверяем cron
crontab -l | grep acme

# Проверяем, что acme.sh знает путь к install-cert
acme.sh --info -d example.com

# Проверяем лог последнего обновления
cat ~/.acme.sh/acme.sh.log | tail -50

Ошибка 5: Права на файлы сертификатов

# acme.sh работает от пользователя, а Nginx запущен от root
# После --install-cert проверяем права:
ls -la /etc/nginx/ssl/

# Если нужно — исправляем
chown root:root /etc/nginx/ssl/*.key /etc/nginx/ssl/*.crt
chmod 600 /etc/nginx/ssl/*.key
chmod 644 /etc/nginx/ssl/*.crt

Best Practices: как делать правильно

Используйте wildcard везде, где можно. Один сертификат *.example.com покрывает все поддомены. Это меньше запросов к Let’s Encrypt, меньше обновлений, меньше мест где что-то может сломаться.

Храните API-ключи безопасно. Не держите токены в переменных окружения в .bashrc в открытом виде. После первого выпуска acme.sh сохраняет их в ~/.acme.sh/account.conf — убедитесь, что права на файл 600. Для production используйте Vault или системный keystore.

# Правильные права на конфиг acme.sh
chmod 600 ~/.acme.sh/account.conf
chmod 700 ~/.acme.sh/

Ограничивайте права API-токенов. Токен Cloudflare для acme.sh должен иметь права только на Zone:DNS:Edit для конкретных зон. Не давайте полный доступ к аккаунту.

Тестируйте на staging. Перед массовым выпуском всегда проверяйте на staging Let’s Encrypt. Лимиты там выше, а ошибки конфигурации лучше поймать заранее:

acme.sh --issue --dns dns_cf \
  -d example.com -d '*.example.com' \
  --staging --debug

Мониторьте истечение. Даже с автообновлением имеет смысл настроить внешний мониторинг срока действия — через UptimeRobot, Zabbix, или простой скрипт:

#!/bin/bash
# Проверка оставшихся дней до истечения сертификата
DOMAIN="example.com"
EXPIRY=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null \
  | openssl x509 -noout -enddate 2>/dev/null \
  | cut -d= -f2)

EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))

echo "$DOMAIN: $DAYS_LEFT days until expiry"
if [ $DAYS_LEFT -lt 14 ]; then
  echo "WARNING: Certificate expires soon!"
  exit 1
fi

Версионируйте конфиги. Скрипты выпуска, списки доменов, конфиги Nginx — держите это в Git. Восстановление после аварии или переезд на новый сервер займёт минуты, а не часы.


FAQ

Чем acme.sh лучше certbot?

acme.sh не требует Python и root-прав, поддерживает 150+ DNS-провайдеров из коробки, занимает меньше места и легче поддаётся автоматизации. Certbot лучше если нужен плагин --nginx для автонастройки конфигов или если команда уже привыкла к certbot.

Можно ли использовать acme.sh без root?

Да, и это одно из ключевых преимуществ. acme.sh устанавливается и работает полностью в домашней директории пользователя. Root нужен только если вы хотите писать сертификаты в системные директории (/etc/nginx/ssl/) — для этого используйте sudo только в --reloadcmd.

acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/example.key \
  --fullchain-file /etc/nginx/ssl/example.crt \
  --reloadcmd "sudo systemctl reload nginx"

Как использовать acme.sh в Docker?

# Официальный образ
<a class="wpil_keyword_link" title="Docker" href="https://it-apteka.com/tag/docker/" target="_blank" rel="noopener" data-wpil-keyword-link="linked" data-wpil-monitor-id="875">docker</a> run --rm -it \
  -v /path/to/acme:/acme.sh \
  -e CF_Token="токен" \
  -e CF_Account_ID="account_id" \
  neilpang/acme.sh --issue \
    --dns dns_cf \
    -d example.com \
    -d '*.example.com'

Для постоянного использования монтируйте /acme.sh как volume — туда сохраняются сертификаты и конфиги.

Что делать, если DNS-провайдер не поддерживается?

Два варианта: DNS alias mode (делегируете _acme-challenge на поддомен у поддерживаемого провайдера) или ручной режим (--dns без API — acme.sh покажет нужную TXT-запись, вы добавляете её вручную, затем продолжаете выпуск). Последнее не подходит для автоматизации, но работает для разовых задач.


Заключение

acme.sh — это правильный инструмент для инфраструктурной автоматизации SSL. Когда доменов больше пяти, когда нужны wildcard-сертификаты, когда сервисы в Docker и нет желания открывать порт 80 на каждом контейнере — DNS challenge через acme.sh превращает рутину в однократно написанный скрипт.

Что мы разобрали в этой статье:

  • Почему HTTP-01 challenge и certbot плохо масштабируются.
  • Как работает DNS-01 challenge и почему он идеален для wildcard.
  • Полный цикл: установка acme.sh → выпуск wildcard через Cloudflare → установка в Nginx → автообновление.
  • Готовый bash-скрипт для массового выпуска SSL на десятки доменов.
  • Типичные ошибки: токены, задержки DNS, rate limits — и как их лечить.

Начните с одного домена, убедитесь что всё работает на staging, потом запускайте массовый выпуск. Один раз настроенный pipeline работает годами без вашего участия.

Пишите в комментарии — какой DNS-провайдер используете, на каких объёмах работаете. Интересно собрать статистику по российской аудитории.

over_dude
Author: over_dude

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх