Можно ли использовать свой публичный домен для локальных сервисов с валидными SSL‑сертификатами и при этом не открывать их в интернет?
Коротко: да. Главная идея — разделить видимость DNS (split‑horizon) и получить сертификаты через DNS‑валидацию (dns‑01), чтобы не делать локальный сервис общедоступным по HTTP. Ниже — простое объяснение проблемы, варианты решений и практические шаги для реализации.
User -> Cloudflare Edge -> cloudflared -> reverse proxy -> service A
Проблема простыми словами
У вас есть публичный домен (например, example.com
). Некоторые сервисы вы хотите открывать в интернет (service A) через Cloudflare + cloudflared. Другие сервисы должны быть доступны только внутри локальной сети (service B). Но вы хотите, чтобы локальные сервисы использовали корректные, публичные SSL‑сертификаты (чтобы не приходилось вручную устанавливать самоподписанные сертификаты на каждое устройство и избежать проблем с iOS и «.lan» именами).
Препятствие: большинство публичных CA (например, Let’s Encrypt) перед выдачей сертификата проверяют владение доменом. Стандартная проверка http‑01 требует, чтобы ACME‑сервер мог достучаться по HTTP к домену — т.е. сервис должен быть доступен извне. Это не подходит, если вы хотите, чтобы сервис был локальным и недоступным снаружи.
Ключевая идея решения
Использовать DNS‑валидацию (dns‑01) для получения сертификатов. В этом случае CA не пытается подключиться к вашему сервису — она проверяет, что вы контролируете DNS записи домена, добавив специальную TXT‑запись. Это позволяет получить публичный сертификат даже для имени, которое не имеет A/AAAA записи в публичной зоне или вообще не доступно извне.
Параллельно сделать так, чтобы внутри вашей сети DNS резолвил имя сервисов на локальные IP (split‑horizon / внутренний DNS — например, AdGuard Home). Внешняя DNS‑зона не будет указывать на локальный сервис, поэтому он не будет доступен извне. Достаточно автоматизировать получение и обновление сертификатов на машине, где работает 리верс‑прокси (например, Caddy), и проксировать к локальному сервису.
Варианты и детальное описание
Ниже — два основных варианта получения валидных сертификатов для локальных сервисов под вашим публичным доменом.
Вариант 1 (рекомендуемый): ACME DNS‑01 + автоматизация через API провайдера (например, Cloudflare)
Суть: используйте DNS‑01 и автоматизируйте добавление TXT‑записей через API Cloudflare. Это позволяет получать сертификаты без выставления ваших локальных хостов в интернет.
Плюсы:
- Полная автоматизация получения и обновления сертификатов.
- Не требуется открывать порт 80/443 наружу для локального сервиса.
- Можно получить wildcard‑сертификат (*.example.com) (всё равно требуется DNS‑01), что удобно для множества локальных субдоменов.
Минусы/внимание:
- Нужно дать автоматизированный доступ к DNS через API — создать токен с минимальными правами (редактирование DNS) и хранить его безопасно.
- Учтите лимиты CA (например, Let’s Encrypt) и правила выдачи wildcard‑сертификатов.
Шаги (примерный план):
- Создайте API‑токен в Cloudflare с правами на изменение DNS для нужной зоны.
- Настройте ваш ACME‑клиент (Caddy, Certbot + плагин, acme.sh, lego и т. п.) использовать Cloudflare API для dns‑01. Большинство клиентов поддерживают это.
- Запросите сертификат для нужного имени
service_b.example.com
или для*.example.com
(если нужен wildcard). Клиент создаст TXT‑запись, CA подтвердит владение, выдаст сертификат и клиент удалит TXT‑запись. - На внутреннем DNS (AdGuard Home) создайте A‑запись
service_b.example.com
→ локальный IP хоста с сервисом B. Внешняя (публичная) зона НЕ должна указывать на этот IP (или не содержать вообще A‑запись для этого имени), чтобы сервис оставался недоступным извне. - Настройте локальный реверс‑прокси (Caddy/Traefik/Nginx) на этом хосте так, чтобы он использовал полученный сертификат и проксировал трафик на соответствующий порт сервиса B.
- Ограничьте доступ на уровне файрвола: разрешите доступ к сервису B только из локальной сети (или конкретного подсети), и убедитесь, что внешние запросы блокируются.
Заметки по iOS и «.lan»: используйте публичный домен (service_b.example.com
), а не service_b.hostname.lan
. iOS может проблемно работать с нестандартными TLD и mDNS именами; «example.com» через внутренний DNS работает гораздо надежнее.
Вариант 2: manual DNS‑01 (ручная вставка TXT) или http‑01 с исключением
Если вы не хотите давать доступ через API, можно выполнять dns‑01 вручную: ACME клиент выдаст токен, вы вручную создадите TXT‑запись и затем получите сертификат. Это неудобно для автопродления, но работает.
Альтернатива с http‑01: теоретически можно сделать http‑01, если на публичном конце для имени существует небольшой HTTP‑сервер, который обрабатывает только /.well-known/acme-challenge
, а весь остальной трафик блокируется или не доступен. Но это сложнее и ненадежно, если вы хотите, чтобы сервис полностью был локальным и закрытым.
Вариант 3: внутренняя PKI (private CA) с автоматическим развёртыванием доверия)
Можно поднять внутренний CA и развернуть корневой сертификат на всех устройствах (через MDM для iOS/macOS, групповые политики для Windows, ручная установка для отдельных устройств). Это решает задачу, но требует управления доверием на клиентских устройствах — часто нежелательно для домашних/разнородных устройств.
Практические советы и «подводные камни»
- Wildcard сертификаты выдаются Let’s Encrypt только через dns‑01. Если планируете много локальных субдоменов, это удобно.
- Не используйте «.local» или другие специальные TLD для публичных сертификатов — CA не выдадут сертификат для «.local», и с iOS будут проблемы.
- Настройте внутренний DNS (AdGuard Home или другой) таким образом, чтобы он отдавал локальные адреса для субдоменов вашего домена; при этом публичная (Cloudflare) зона не должна указывать на эти адреса.
- Для автоматизации обновлений сертификатов разверните ACME‑клиент на том хосте, где будет храниться сертификат и реверс‑прокси, чтобы он мог автоматически выполнять dns‑01 через Cloudflare API.
- Создавайте API‑токен Cloudflare с минимально необходимыми правами (только редактирование DNS для нужной зоны) и храните его в безопасном месте (секреты/переменные окружения).
- Ограничьте доступ сетевыми правилами и/или настройками прокси (чтобы даже случайно не пробросить сервис наружу).
Резюме — простой рекомендуемый рабочий рецепт
- Заведите сервисы на именах вида
service_a.example.com
(публичный) иservice_b.example.com
(локальный). - Для публичного сервиса используйте Cloudflare Edge + cloudflared как раньше.
- Для локального сервиса используйте ACME dns‑01 через Cloudflare API (или другой DNS‑провайдер) для автоматического получения/обновления сертификатов.
- На внутреннем DNS (AdGuard Home) делайте A‑запись
service_b.example.com
→ локальный IP; в публичной зоне такой A‑записи нет (или она указывает в никуда), чтобы сервис был недоступен извне. - Запретите доступ к сервису B из внешней сети на уровне сети/фаервола.
- Используйте публичные имена, а не
.lan
/.local, чтобы избежать проблем с iOS.
Выводы и рекомендации
Лучший и наиболее простой путь — это dns‑01 в связке с внутренним DNS (split‑horizon) и автоматизацией через API вашего DNS‑провайдера (Cloudflare). Это даёт валидные публичные сертификаты без необходимости выставлять локальные сервисы в интернет и без ручной установки сертификатов на все устройства. Для домашних/корпоративных сетей с управляемыми устройствами возможен вариант внутреннего CA, но он сложнее в поддержке.
Если хотите, могу привести пример конфигурации для Caddy/Certbot с Cloudflare API (показать, какие переменные окружения задать и как настроить AdGuard Home), или подсказать, какие права выдать Cloudflare‑токен. Скажите, какой ACME‑клиент вы предпочитаете.