Wildcard SSL-сертификат: один для всех поддоменов
🔐 Wildcard SSL-сертификат: один для всех поддоменов
Зачем он нужен
Если у вас десятки сервисов на поддоменах (grafana.example.ru, vpn.example.ru, ha.example.ru, npm.example.ru), управлять отдельными сертификатами для каждого — боль. Wildcard-сертификат покрывает все поддомены первого уровня:
*.example.ru → grafana.example.ru, vpn.example.ru, ha.example.ru и т.д.
Важно: wildcard не покрывает вложенные уровни вида
a.b.example.ru, и не покрывает сам apex-доменexample.ru. Поэтому почти всегда выпускают два SAN:*.example.ruиexample.ru.
Как это работает (коротко)
Wildcard можно выпустить только через DNS-01 challenge (ACME-протокол). CA (напр., Let’s Encrypt) проверяет, что вы управляете доменом, ища TXT-запись _acme-challenge.example.ru с нужным токеном.
Варианты валидации:
- Ручная: разово добавить TXT и подтвердить.
- Автоматическая: через API провайдера DNS (Cloudflare, DO, Yandex, Route53 и др.).
- acme-dns: отдельный поддомен, делегированный для автоматизации TXT-записей.
Что выбрать: Certbot или acme.sh?
Оба отличные. Кратко:
- Certbot — «дефолт» от Let’s Encrypt, много плагинов DNS.
- acme.sh — очень лёгкий shell-скрипт, куча DNS-интеграций, просто ставится в cron, удобен в Docker.
Я покажу оба пути + кейсы для Nginx, Traefik, Nginx Proxy Manager и OPNsense.
Подготовка (чек-лист)
- Домен и доступ к DNS-зоне.
- Определитесь, что выпускаете:
example.ru+*.example.ru. - Решите, где хранить/использовать ключи: Nginx/Traefik/NPM/OPNsense/HAProxy.
- Выберите тип ключа:
- ECDSA P-256 (короче и быстрее) — мой фаворит.
- (Опционально) параллельно RSA 2048 для старых клиентов.
- Решите способ автоматизации DNS-валидации (API/плагин/Delegated acme-dns).
Вариант A: Certbot + DNS API (пример с Cloudflare)
1) Устанавливаем Certbot и плагин
1
2
3
# Debian/Ubuntu (пример)
sudo apt-get update
sudo apt-get install -y certbot python3-certbot-dns-cloudflare
2) Токен Cloudflare (минимальные права)
- Zone → DNS: Edit
- Zone → Zone: Read
Сохраните токен в файле, доступном только root:
1
2
3
4
5
sudo mkdir -p /etc/letsencrypt
sudo bash -c 'cat > /etc/letsencrypt/cloudflare.ini <<EOF
dns_cloudflare_api_token = CF_TOKEN_HERE
EOF'
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
3) Выпускаем wildcard + apex
1
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini -d "example.ru" -d "*.example.ru" --preferred-chain "ISRG Root X1" --key-type ecdsa --agree-tos -m you@example.ru --no-eff-email
Где искать результаты:
1
2
3
4
/etc/letsencrypt/live/example.ru/
├─ fullchain.pem # цепочка+сертификат
├─ privkey.pem # приватный ключ
└─ chain.pem
4) Автопродление + перезапуск сервисов
Let’s Encrypt выдаёт на 90 дней, обновляйте каждые ~60.
1
2
# Cron (два раза в день пробует, обновит только когда нужно)
echo '0 3,15 * * * root certbot renew --deploy-hook "systemctl reload nginx"' | sudo tee /etc/cron.d/certbot
Если Nginx Proxy Manager, Traefik или Docker — см. ниже разделы интеграции.
Вариант B: Certbot + ручной DNS (разово)
Подходит, если нет API у провайдера и нужно «просто один раз».
1
sudo certbot certonly --manual --preferred-challenges dns -d "example.ru" -d "*.example.ru" --key-type ecdsa --agree-tos -m you@example.ru
Certbot попросит добавить TXT в _acme-challenge.example.ru. Добавьте две TXT (одна для apex, одна для wildcard), подождите 1–2 минуты (TTL/propagation), далее Enter.
Минусы: обновление вручную (неудобно).
Вариант C: acme.sh (ультралёгкий и гибкий)
1) Установка
1
2
curl https://get.acme.sh | sh -s email=you@example.ru
# После установки перезайдите в shell или source ~/.bashrc
2) Cloudflare токен и выпуск
1
2
3
export CF_Token="CF_TOKEN_HERE"
export CF_Account_ID="(не обяз.)"
acme.sh --issue --dns dns_cf -d example.ru -d '*.example.ru' --keylength ec-256
3) Установка (deploy) в вашу систему
Пример — развернуть в каталог Nginx и перезагрузить:
1
2
sudo mkdir -p /etc/nginx/ssl/example.ru
acme.sh --install-cert -d example.ru --ecc --fullchain-file /etc/nginx/ssl/example.ru/fullchain.pem --key-file /etc/nginx/ssl/example.ru/privkey.pem --reloadcmd "systemctl reload nginx"
acme.sh сам ставит cron на продление.
Интеграция: Nginx / Nginx Proxy Manager / Traefik / OPNsense
🔸 Nginx (стендэлон)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 443 ssl http2;
server_name grafana.example.ru;
ssl_certificate /etc/nginx/ssl/example.ru/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.ru/privkey.pem;
# (опционально) добавить RSA и ECDSA вместе:
# ssl_certificate /etc/nginx/ssl/example.ru/fullchain-ecdsa.pem;
# ssl_certificate_key /etc/nginx/ssl/example.ru/privkey-ecdsa.pem;
# ssl_certificate /etc/nginx/ssl/example.ru/fullchain-rsa.pem;
# ssl_certificate_key /etc/nginx/ssl/example.ru/privkey-rsa.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
После обновления сертификата — systemctl reload nginx.
🔸 Nginx Proxy Manager (NPM)
- В NPM → SSL Certificates → Add → Custom.
- Вставьте
fullchain.pemиprivkey.pem(из Certbot/acme.sh). - Назначьте сертификат нужным Proxy Hosts.
- Для автопродления:
- Либо используйте DNS-Challenge прямо в NPM (если DNS-провайдер поддерживается).
- Либо обновляйте сертификат вне NPM (Certbot/acme.sh) и скриптом через NPM API переимпортируйте (hook
--deploy-hook).
🔸 Traefik (Docker/K8s)
Проще всего — встроенный ACME (dnsChallenge). Пример traefik.yml:
1
2
3
4
5
6
7
8
9
10
11
certificatesResolvers:
le-dns:
acme:
email: you@example.ru
storage: /letsencrypt/acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
keyType: EC256
И роутер с SAN:
1
2
3
4
5
6
tls:
certResolver: le-dns
domains:
- main: "example.ru"
sans:
- "*.example.ru"
Передайте токены провайдера как env CF_DNS_API_TOKEN и т.п.
🔸 OPNsense (ACME Client + HAProxy/напрямую)
- Установите плагин ACME Client.
- Добавьте Account (Let’s Encrypt / ZeroSSL).
- Создайте Certificate:
- FQDN:
example.ru - Alt Names:
*.example.ru - Validation Method: DNS-01 через ваш DNS-провайдер (введите API токен).
- FQDN:
- В Actions привяжите команды: перезапустить HAProxy/Nginx на OPNsense (если используете).
- В HAProxy/Frontend укажите путь к сертификату, который ACME Client положит в систему сертификатов OPNsense.
Плюс OPNsense: всё обновляется автоматически, включая DNS-валидацию.
Лучшие практики (важно!)
- ECDSA по умолчанию, RSA как fallback при необходимости (старые клиенты).
- Выпускайте SAN:
example.ru+*.example.ru. - Держите автопродление: cron/systemd timers/Traefik auto.
- Перезагрузка/перечтение сервисов post-renewal:
--deploy-hook/--reloadcmd. - Разделяйте права: токен DNS с минимальными правами (только DNS Edit).
- Проверьте CAA-записи в зоне: разрешите выбранный CA (например, Let’s Encrypt).
- Уменьшите TTL TXT на время валидации до 60-120 секунд, потом верните обратно.
- Не коммитьте ключи в Git. Закрывайте права
600на приватные ключи. - Для массового хостинга подумайте о обособлении ключей на каждом узле (секрет-менеджеры/Ansible Vault/HashiCorp Vault).
Проверка сертификата
Быстрая проверка из CLI
1
echo | openssl s_client -connect grafana.example.ru:443 -servername grafana.example.ru 2>/dev/null | openssl x509 -noout -issuer -subject -dates
Проверка цепочки
1
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/nginx/ssl/example.ru/fullchain.pem
Проверка HTTP/2/TLS
1
curl -Iv https://grafana.example.ru
(Опционально) Прогон через SSL Labs (браузером) для внешней оценки.
Траблшутинг
No TXT record found for _acme-challenge
Подождите propagation 1–3 минуты (или дольше у некоторых DNS), убедитесь, что добавили в нужную зону, без лишних кавычек.CAA record prevents issuance
В зоне стоят CAA, разрешающие другой CA. Добавьте CAA, разрешающий вашего CA (например, Let’s Encrypt).Too many certificates already issued(rate limit)
У Let’s Encrypt есть недельные лимиты на домен. Разворачивайтесь на staging во время тестов:1 2
--test-cert # certbot acme.sh --staging # acme.sh
Обновление не импортируется в NPM
Используйте--deploy-hookдля автоматического реимпорта через API NPM или включите native DNS-challenge NPM.Traefik не берёт wildcard
Проверьте, что включёнdnsChallenge, а неhttpChallenge. Для wildcard только DNS.- OPNsense ACME не проходит DNS-валидацию
Проверьте корректность DNS API токена/прав и что выбран верный DNS-провайдер в плагине.
Примеры автоматизации (на сладкое)
Certbot: systemd-таймер вместо cron
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot Renew
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --deploy-hook "systemctl reload nginx"
# /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Twice daily renew of Let's Encrypt certificates
[Timer]
OnCalendar=*-*-* 03,15:00:00
Persistent=true
[Install]
WantedBy=timers.target
1
sudo systemctl enable --now certbot-renew.timer
acme.sh: dual-stack (ECDSA + RSA) и деплой
1
2
3
4
5
6
7
8
9
# ECDSA
acme.sh --issue --dns dns_cf -d example.ru -d '*.example.ru' --keylength ec-256
# RSA
acme.sh --issue --dns dns_cf -d example.ru -d '*.example.ru' --keylength 2048
# Deploy обеих
acme.sh --install-cert -d example.ru --ecc --fullchain-file /etc/nginx/ssl/example.ru/fullchain-ecdsa.pem --key-file /etc/nginx/ssl/example.ru/privkey-ecdsa.pem --reloadcmd "systemctl reload nginx"
acme.sh --install-cert -d example.ru --fullchain-file /etc/nginx/ssl/example.ru/fullchain-rsa.pem --key-file /etc/nginx/ssl/example.ru/privkey-rsa.pem --reloadcmd "systemctl reload nginx"
И в Nginx:
1
2
3
4
ssl_certificate /etc/nginx/ssl/example.ru/fullchain-ecdsa.pem;
ssl_certificate_key /etc/nginx/ssl/example.ru/privkey-ecdsa.pem;
ssl_certificate /etc/nginx/ssl/example.ru/fullchain-rsa.pem;
ssl_certificate_key /etc/nginx/ssl/example.ru/privkey-rsa.pem;
Итоги
- Wildcard даёт один сертификат для всех поддоменов первого уровня — меньше рутины, проще масштабирование.
- Для wildcard обязателен DNS-01 challenge → используйте DNS API или Traefik/NPM/OPNsense с поддержкой DNS-валидации.
- Настройте автопродление и перезагрузку сервисов hook’ами.
- Выбирайте ECDSA как основной, добавляйте RSA для максимальной совместимости при необходимости.
