У вас 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 всё иначе:
- Ваш ACME-клиент (acme.sh) обращается к Let’s Encrypt и говорит: «Хочу сертификат для example.com».
- Let’s Encrypt отвечает: «Добавь TXT-запись
_acme-challenge.example.comсо значениемХХХ_уникальный_токен_ХХХ». - acme.sh через API вашего DNS-провайдера автоматически создаёт эту TXT-запись.
- acme.sh ждёт, пока запись распространится (TTL + задержки DNS).
- Let’s Encrypt делает DNS-запрос, находит TXT-запись, подтверждает владение доменом, выдаёт сертификат.
- 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-провайдер используете, на каких объёмах работаете. Интересно собрать статистику по российской аудитории.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



