Let's Encrypt на 200 доменах: автоматизация SSL без ручной рутины

Задача клиента: 200 доменов и сертификаты, которые забывают продлить

Веб-студия «ВебСтудия» управляет 200 сайтами клиентов на 8 серверах. Исторически SSL-сертификаты получали вручную: инженер запускал certbot, копировал конфигурацию, перезапускал Nginx. Сертификаты Let's Encrypt действуют 90 дней — при 200 доменах это означает 2-3 продления в день.

Неизбежно что-то забывалось. За последний квартал 12 сайтов показали посетителям предупреждение «Ваше подключение не защищено» из-за просроченных сертификатов. Клиенты теряли доверие и заказы — один интернет-магазин оценил потери в 300 000 рублей за два дня простоя.

Специалисты itfresh.ru развернули полностью автоматизированную систему: получение, продление, деплой и мониторинг SSL-сертификатов для всех 200 доменов без единого ручного действия.

Certbot vs acme.sh: выбор инструмента

Два основных клиента ACME-протокола — certbot (от EFF) и acme.sh (shell-скрипт). Мы выбрали acme.sh для «ВебСтудия» по нескольким причинам.

Критерийcertbotacme.sh
ЗависимостиPython, snap/pipТолько bash + curl
DNS-провайдеры~10 плагинов~150 DNS API
WildcardДа (DNS-01)Да (DNS-01)
Автопродлениеsystemd timer / cronВстроенный cron
Мультидоменные SANДа (-d -d -d)Да (-d -d -d)
Поддержка серверовNginx, Apache плагиныStandalone, webroot, DNS
Размер~50 МБ (snap)~200 КБ
# Установка acme.sh
$ curl https://get.acme.sh | sh -s email=admin@webstudio.ru

# Или установка certbot (для сравнения)
$ apt install certbot python3-certbot-nginx

Для проектов с Nginx-плагинами и простой конфигурацией certbot удобнее — он автоматически правит конфиги Nginx. Для масштабных деплоев с множеством DNS-провайдеров acme.sh гибче.

HTTP-01 vs DNS-01: стратегия валидации

Let's Encrypt проверяет владение доменом через challenges — задания, подтверждающие контроль над доменом.

HTTP-01 — файл размещается по пути /.well-known/acme-challenge/ на веб-сервере. Простой способ, но не работает для wildcard и требует открытый порт 80.

# Certbot с HTTP-01 (автоматическая настройка Nginx)
$ certbot --nginx -d example.com -d www.example.com

# acme.sh с HTTP-01 через webroot
$ acme.sh --issue -d example.com -d www.example.com \
    --webroot /var/www/example.com/public

# Nginx: настройка location для challenge
server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/acme-challenge;
        try_files $uri =404;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

DNS-01 — TXT-запись добавляется в DNS. Единственный способ получить wildcard-сертификат. Работает даже если сервер недоступен из интернета.

# acme.sh с DNS-01 через Cloudflare API
$ export CF_Token="your_cloudflare_api_token"
$ export CF_Zone_ID="your_zone_id"

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

# certbot с DNS-01 через Cloudflare плагин
$ apt install python3-certbot-dns-cloudflare

$ cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = your_cloudflare_api_token
EOF
$ chmod 600 /etc/letsencrypt/cloudflare.ini

$ certbot certonly --dns-cloudflare \
    --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
    -d example.com -d '*.example.com'

Для «ВебСтудия» мы использовали комбинацию: HTTP-01 для обычных доменов (быстрее, не зависит от DNS-провайдера) и DNS-01 для wildcard-сертификатов и доменов за Cloudflare.

Wildcard и SAN-сертификаты

Wildcard-сертификат (*.example.com) покрывает все поддомены одного уровня. SAN (Subject Alternative Name) позволяет включить несколько доменов в один сертификат.

# Wildcard + основной домен (нужен DNS-01)
$ acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf

# SAN-сертификат для нескольких доменов одного клиента
$ acme.sh --issue \
    -d shop.client1.ru \
    -d www.shop.client1.ru \
    -d api.client1.ru \
    -d admin.client1.ru \
    --webroot /var/www/client1/public

# Certbot: мультидоменный сертификат
$ certbot certonly --nginx \
    -d shop.client1.ru \
    -d www.shop.client1.ru \
    -d api.client1.ru

Ограничения Let's Encrypt, которые критичны на масштабе:

  • 50 сертификатов на домен в неделю (Certificates per Registered Domain).
  • 100 доменов в одном SAN-сертификате (Names per Certificate).
  • 300 новых заказов в 3 часа (New Orders).
  • 5 дублирующих сертификатов в неделю (Duplicate Certificate).
  • 5 неудачных валидаций на аккаунт/хост/час (Failed Validation).
# Тестирование на staging-сервере (нет rate limits)
$ acme.sh --issue -d test.example.com --dns dns_cf --staging

# certbot staging
$ certbot certonly --nginx -d test.example.com --staging

# Просмотр staging-сертификата
$ openssl x509 -in /etc/letsencrypt/live/test.example.com/cert.pem \
    -text -noout | grep Issuer
    Issuer: CN = (STAGING) Counterfeit Cashew R10

Для «ВебСтудия» с 200 доменами мы группировали домены одного клиента в SAN-сертификаты, снижая общее количество сертификатов с 200 до 85.

Автопродление: systemd timer + deploy hooks

Автоматическое продление — ядро всей системы. Мы настроили systemd timer вместо cron для лучшей интеграции с логированием и мониторингом.

# /etc/systemd/system/acme-renew.timer
[Unit]
Description=Renew ACME certificates

[Timer]
# Дважды в день (рекомендация Let's Encrypt)
OnCalendar=*-*-* 02,14:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

# /etc/systemd/system/acme-renew.service
[Unit]
Description=Renew ACME certificates
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=root
ExecStart=/root/.acme.sh/acme.sh --cron --home /root/.acme.sh
ExecStartPost=/usr/bin/nginx -t
ExecStartPost=/usr/bin/systemctl reload nginx

$ systemctl enable --now acme-renew.timer

Deploy hooks выполняются после успешного получения сертификата. Для каждого домена мы настроили автоматическую установку:

# acme.sh: установка сертификата с deploy hook
$ acme.sh --install-cert -d example.com \
    --cert-file /etc/nginx/ssl/example.com/cert.pem \
    --key-file /etc/nginx/ssl/example.com/key.pem \
    --fullchain-file /etc/nginx/ssl/example.com/fullchain.pem \
    --reloadcmd "systemctl reload nginx"

# Скрипт массовой установки для всех доменов
#!/bin/bash
# /opt/scripts/install-certs.sh
for DOMAIN_DIR in /etc/nginx/ssl/*/; do
    DOMAIN=$(basename "$DOMAIN_DIR")
    acme.sh --install-cert -d "$DOMAIN" \
        --cert-file "${DOMAIN_DIR}cert.pem" \
        --key-file "${DOMAIN_DIR}key.pem" \
        --fullchain-file "${DOMAIN_DIR}fullchain.pem" \
        --reloadcmd "systemctl reload nginx"
done

Для certbot автопродление уже настроено при установке:

# certbot создаёт свой systemd timer
$ systemctl list-timers | grep certbot
certbot.timer    *-*-* 00,12:00:00    certbot.service

# Post-renewal hook для перезагрузки Nginx
$ cat > /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh << 'EOF'
#!/bin/bash
nginx -t && systemctl reload nginx
EOF
$ chmod +x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh

Мониторинг сертификатов и OCSP stapling

Автоматизация без мониторинга — бомба замедленного действия. Мы настроили проактивный мониторинг сроков действия сертификатов.

#!/bin/bash
# /opt/scripts/cert-monitor.sh
TG_BOT="5551234567:AAHxxx"
TG_CHAT="-100123456789"
WARN_DAYS=14
CRIT_DAYS=7

for CERT_DIR in /etc/nginx/ssl/*/; do
    DOMAIN=$(basename "$CERT_DIR")
    CERT="${CERT_DIR}fullchain.pem"
    [ -f "$CERT" ] || continue

    # Дата истечения
    EXPIRY=$(openssl x509 -enddate -noout -in "$CERT" | cut -d= -f2)
    EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
    NOW_EPOCH=$(date +%s)
    DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

    if [ "$DAYS_LEFT" -lt "$CRIT_DAYS" ]; then
        MSG="🔴 CRITICAL: $DOMAIN — сертификат истекает через ${DAYS_LEFT} дней!"
    elif [ "$DAYS_LEFT" -lt "$WARN_DAYS" ]; then
        MSG="🟡 WARNING: $DOMAIN — сертификат истекает через ${DAYS_LEFT} дней"
    else
        continue
    fi

    curl -s "https://api.telegram.org/bot${TG_BOT}/sendMessage" \
        -d chat_id="${TG_CHAT}" -d text="${MSG}" > /dev/null
done

OCSP stapling ускоряет TLS-handshake: сервер сам получает статус отзыва сертификата и отдаёт его клиенту, вместо того чтобы клиент обращался к OCSP-серверу.

# Nginx: включение OCSP stapling
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/key.pem;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/example.com/fullchain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;

    # Дополнительные параметры безопасности
    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;
    ssl_prefer_server_ciphers off;

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000" always;
}

# Проверка OCSP stapling
$ openssl s_client -connect example.com:443 -status < /dev/null 2>&1 | \
    grep -A 5 'OCSP Response Status'
OCSP Response Status: successful (0x0)
OCSP Response Data:
    Response Status: good
    This Update: Apr  5 02:00:00 2026 GMT
    Next Update: Apr 12 02:00:00 2026 GMT

Результаты и шаблон конфигурации

После автоматизации SSL для «ВебСтудия» ни один сертификат не просрочен за 6 месяцев эксплуатации.

МетрикаДоПосле
Просроченных сертификатов за квартал120
Время на управление SSL в неделю3-4 часа0 (мониторинг — 5 мин)
Добавление нового домена15-30 минут1 команда, 30 секунд
Количество сертификатов200 (отдельные)85 (SAN-группы)
TLS Handshake time (OCSP)~180 мс (клиент → OCSP)~45 мс (stapled)

Шаблон для быстрого добавления нового домена:

#!/bin/bash
# /opt/scripts/add-domain-ssl.sh
DOMAIN=$1
WEBROOT=${2:-/var/www/$DOMAIN/public}

[ -z "$DOMAIN" ] && { echo "Usage: $0 domain.com [webroot]"; exit 1; }

# Создание директории для сертификатов
mkdir -p /etc/nginx/ssl/$DOMAIN

# Получение сертификата
acme.sh --issue -d $DOMAIN -d www.$DOMAIN --webroot $WEBROOT

# Установка с deploy hook
acme.sh --install-cert -d $DOMAIN \
    --cert-file /etc/nginx/ssl/$DOMAIN/cert.pem \
    --key-file /etc/nginx/ssl/$DOMAIN/key.pem \
    --fullchain-file /etc/nginx/ssl/$DOMAIN/fullchain.pem \
    --reloadcmd "systemctl reload nginx"

echo "SSL for $DOMAIN configured. Test: https://$DOMAIN"

Если ваша компания управляет десятками или сотнями доменов и хочет забыть о проблемах с SSL-сертификатами — обращайтесь к специалистам itfresh.ru. Мы настроим автоматизацию от получения до мониторинга.

Часто задаваемые вопросы

Подвоха нет: Let's Encrypt — некоммерческий центр сертификации, спонсируемый крупными компаниями (Google, Mozilla, Cisco). Ограничения: срок действия 90 дней (для мотивации автоматизации), отсутствие EV/OV-сертификатов (только DV), rate limits на количество выпусков.
Certbot удобнее для единичных серверов с Nginx/Apache — плагины автоматически правят конфиги. Acme.sh лучше для масштабных деплоев: 150+ DNS-провайдеров, минимальные зависимости (только bash), меньший размер, гибкие deploy hooks. Для Cloudflare DNS оба работают одинаково хорошо.
Wildcard (*.example.com) требует DNS-01 challenge — автоматическое добавление TXT-записи в DNS. Используйте acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf (для Cloudflare) или аналогичный DNS-плагин для вашего провайдера. HTTP-01 для wildcard не работает.
Подождите неделю (лимиты обновляются per-week) или используйте staging-сервер для тестирования (--staging). Для снижения risk: группируйте домены в SAN-сертификаты, не пересоздавайте существующие сертификаты (используйте --renew), запрашивайте увеличение лимита через форму на сайте Let's Encrypt.
OCSP stapling ускоряет TLS-подключение: сервер сам проверяет статус отзыва сертификата и отдаёт ответ клиенту, экономя отдельный запрос к OCSP-серверу (100-200 мс). Проверка: openssl s_client -connect domain.com:443 -status — ищите 'OCSP Response Status: successful'. В Nginx включается директивой ssl_stapling on.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#lets encrypt#ssl сертификат#certbot#acme.sh#wildcard сертификат#dns-01 challenge#auto renewal#cloudflare dns plugin
Комментарии 0

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

загрузка...