Контекст
30 января, вечер. У fxTunnel есть поддомены — myapp.tunnel.example.com. Работает. Но что если я хочу показать заказчику проект на demo.mycompany.com? Своём домене, а не чужом поддомене. Это совершенно другой уровень.
Кастомные домены — фича, которая превращает self-hosted туннель в полноценную платформу.
Как это работает
Идея простая, реализация — не очень:
1. Пользователь добавляет свой домен в панели (например, app.mycompany.com)
2. Настраивает CNAME-запись, указывающую на сервер fxTunnel
3. fxTunnel проверяет DNS — убеждается, что домен действительно указывает на нас
4. Автоматически получает TLS-сертификат от Let's Encrypt
5. Трафик на app.mycompany.com маршрутизируется в нужный туннель
Для пользователя: добавил домен, подождал минуту, работает. HTTPS из коробки, сертификат обновляется сам.
DNS-верификация
Прежде чем принять домен, нужно убедиться, что он действительно принадлежит пользователю. Проверяем DNS-записи:
- Для поддоменов (3+ уровня, типа app.mycompany.com) — проверяем CNAME. Должен указывать на наш сервер.
- Для apex-доменов (2 уровня, типа mycompany.com) — CNAME нельзя ставить на apex по стандарту DNS. Проверяем A/AAAA-записи — IP должен совпадать с нашим сервером.
Это различие (apex vs subdomain) — частый источник путаницы. Многие пользователи не знают, что CNAME на корневом домене — это нарушение RFC. Поэтому в UI я добавил пошаговую инструкцию: для поддомена — CNAME, для apex — A-запись с IP сервера.
TLS: Let's Encrypt через ACME
Сертификаты получаем автоматически через ACME-протокол (тот же, что использует Let's Encrypt). Go-библиотека autocert делает почти всё сама:
1. Приходит первый запрос на домен
2. autocert видит, что сертификата нет
3. Запрашивает сертификат у Let's Encrypt (HTTP-01 или TLS-ALPN-01 challenge)
4. Let's Encrypt проверяет, что домен указывает на наш сервер
5. Выдаёт сертификат, autocert кэширует его
Сертификаты хранятся в базе данных (не на диске) — это важно для будущего масштабирования. Отдельный HTTPS-listener на порту 443 обслуживает кастомные домены.
Что пришлось написать
Фича затронула 30 файлов, 2346 строк — одна из самых крупных в проекте: - Бэкенд: конфиг, модели БД, DNS-верификация, cert manager, API-хэндлеры (пользовательские + админские), интеграция с HTTP-роутером - Веб-панель: UI для добавления доменов, пошаговая инструкция DNS, статус верификации, админка - GUI-клиент: Wails-сервис, Pinia-стор, обновлённый экран доменов
Грабли
ACME fallback. Первая версия искала сертификат в БД — нет записи, значит ошибка. Но при первом запросе записи ещё нет — нужно дать autocert шанс получить сертификат. Добавил fallback: нет в кэше → пробуем ACME on-demand → сохраняем результат.
TLS-ALPN-01 challenge. Для HTTP-01 challenge Let's Encrypt делает запрос на порт 80. Но у меня порт 80 занят HTTP-туннелями. TLS-ALPN-01 работает через порт 443 — как раз где висит HTTPS-listener. Добавил acme-tls/1 в NextProtos конфигурации TLS.
Apex-домены. Первая версия поддерживала только CNAME (поддомены). Пользователи хотели привязать корневой домен — пришлось добавить проверку A/AAAA-записей и инструкцию с IP сервера.
Итог
30 января (вечер): - Кастомные домены с CNAME и A-записями - Автоматический TLS через Let's Encrypt - DNS-верификация с пошаговой инструкцией в UI - Apex-домены через A-записи - Админка для управления доменами всех пользователей
8288168 feat: add custom domains with CNAME binding and TLS certificates
cac4614 feat(custom-domains): support apex domains via A record
3747c90 fix(tls): fall back to ACME on-demand when cached cert is missing
Теперь пользователь может показать проект на своём домене. С HTTPS. Бесплатно.
В следующей части: крупный рефакторинг — как разбить монолитный server.go на части и не сломать всё.