Прямой ответ
Современные браузерные API (service worker, геолокация, буфер обмена, HTTP/2) работают только по HTTPS, поэтому «https на localhost» — не роскошь, а регулярная задача. Решается она связкой из двух инструментов за десять минут: mkcert создаёт локальный доверенный CA и выпускает от него сертификаты для любых локальных доменов (браузер им доверяет без предупреждений); Caddy поднимает reverse-proxy, который автоматически использует эти сертификаты и раздаёт несколько локальных сервисов по именам вроде app.test на 443 порту. Ресурсы: Caddy в простое потребляет единицы мегабайт — прокси можно держать запущенным постоянно.
Шаг 1. mkcert: доверенные сертификаты без «красного экрана»
mkcert -install # создать локальный CA и добавить в хранилища доверия
mkcert app.test api.app.test # выпустить сертификаты
Как это работает и почему безопасно: mkcert генерирует корневой сертификат, который добавляется в доверенные корневые хранилища вашей ОС/браузера, и подписывает им листовые сертификаты для указанных имён. Доверие существует только на вашей машине, приватный ключ CA лежит в mkcert -CAROOT и никуда не отправляется. Правила гигиены: не передавайте файлы CA другим людям (это ровно тот сценарий, которым пользуются атаки «свой CA в доверенных»); при компрометации машины CA стоит удалить (mkcert -uninstall) и выпустить новый.
Имена доменов выбирайте из зарезервированных TLD: .test (RFC 6761) гарантированно не появится в публичном DNS — в отличие от исторического .dev, который Google делегировал в публичный реестр с обязательным HSTS.
Шаг 2. Caddy: reverse-proxy с автоматическим TLS
Один Caddyfile на весь локальный стек:
app.test {
reverse_proxy localhost:3000
}
api.app.test {
reverse_proxy localhost:8000
}
caddy run — и оба сервиса доступны по HTTPS на стандартном 443 порту с доверенным сертификатом. Что Caddy делает за вас: выпуск и ротация сертификатов (для локальных имён — через свой локальный CA, который достаточно один раз добавить в доверие — caddy trust), HTTP/2, редирект с HTTP. Альтернатива — nginx с ручной раскладкой сертификатов mkcert: работает, но конфигурация в разы длиннее и ротация ручная; для локальных задач Caddy объективно проще.
Связка с Docker-стеком: сервисы из compose остаются на своих localhost-портах, Caddy проксирует их по именам — паттерн стека описан в статье про Docker Compose. Порты 80/443 требуют прав администратора (Linux: setcap или systemd-юнит с ambient capabilities; Windows/macOS — от админа либо порты выше 1024 с редиректом).
Шаг 3. Разрешение имён: hosts против локального DNS
Для 2–3 доменов достаточно /etc/hosts (Windows: C:\Windows\System32\drivers\etc\hosts):
127.0.0.1 app.test api.app.test
Когда поддоменов много (микросервисы *.dev.test), заведите локальный resolver: dnsmasq с address=/test/127.0.0.1 покрывает всю зону одной строкой. Важно понимать: браузеры резолвят имена через ОС, а CLI-утилиты (curl, http-клиенты) — тоже через hosts/resolver, поэтому «в браузере работает, в curl нет» чаще всего означает не сертификат, а DNS.
Когда без локального HTTPS не обойтись
- Service workers и PWA — требуют secure origin;
localhostсчитается таковым, но доменное имя с HTTP — уже нет. - OAuth-редиректы на кастомные домены, cookie с
Secure/SameSite=None, cross-site-сценарии междуapp.testиapi.app.test— корректно тестируются только по HTTPS с настоящими доменами. - HTTP/2 и Server Push-подобные сценарии, WebRTC, доступ с телефона в локальной сети (здесь понадобится сертификат на LAN-IP и тот же mkcert).
Для проверки API без браузера сертификаты mkcert подключаются к CLI: `curl --cacert "$(mkcert -CAROOT)/rootCA.pem" — коллекция таких команд живёт по правилам статьи про тестирование API.
Типовые ошибки и быстрые решения
- «Ваше подключение не защищено» после mkcert — сертификат выпущен до
mkcert -install, либо Firefox использует своё хранилище (mkcert умеет и его, но проверяйте отдельно); перевыпустите сертификат. - Caddy падает с «address already in use» — порт 443 занят Skype/IIS/другим прокси; освободите или смените порт.
- HTTPS работает, но cookie «не липнут» — домен и атрибуты cookie должны соответствовать:
Secureтребует HTTPS (есть),Domain=.app.test— совпадения зоны. - Коллега не может зайти на ваш
app.test— имя резолвится только на вашей машине; для доступа из сети нужен IP/общий DNS и сертификат на это имя. - Самоподписанный сертификат «вручную в браузере» вместо mkcert — работает разово, но ломает fetch/CLI и приучает игнорировать предупреждения TLS — плохая привычка; локальный CA дешевле.
Локальный git-хостинг с веб-интерфейсом тоже выигрывает от этой схемы: Gitea за Caddy получает HTTPS и красивое имя вместо localhost:3000 — установка в статье про Gitea.
FAQ
Нужен ли локальный HTTPS, если браузер считает localhost безопасным? Для одиночного сервиса на ` — нет. Он появляется, когда нужны несколько сервисов с разными доменами, cross-site-куки, доступ с других устройств или поведение «как в проде».
Чем mkcert отличается от самоподписанного сертификата openssl? Ровно одной вещью, которая решает всё: mkcert создаёт CA и добавляет его в доверие — браузеры и CLI перестают ругаться. Самоподписанный сертификат придётся вручную «протыкивать» в каждом инструменте.
Безопасно ли держать root CA mkcert на машине? Это локальный CA только для ваших доменов; риск эквивалентен любому доверенному корню в системе. Не экспортируйте его и не публикуйте ключ — и сценарий остаётся безопасным.
Caddy или Traefik для локальной разработки? Traefik мощнее в динамических Docker-сценариях (автообнаружение сервисов по лейблам), Caddy проще для статичных конфигов из 2–10 имён. Для типового локального стека Caddyfile понятнее; Traefik имеет смысл, если сервисы постоянно появляются и исчезают.
Сценарии из практики: три схемы, которые повторяются
Первая — фронтенд и API на разных доменах: app.test проксирует dev-сервер Vite, api.app.test — backend; браузер видит настоящую cross-site-схему, и проблемы с CORS/куками проявляются локально, а не после деплоя. Вторая — несколько проектов одновременно: каждый проект получает своё имя (shop.test, blog.test) на 443 порту вместо запоминания портов 3000/3001/8080; переключение между проектами перестаёт путать вкладки. Третья — вебхуки и интеграции: сервис на вашем ноутбуке получает вызовы через сервер-посредник с туннелем (схема описана в статье про SSH-туннели линии), а локальный Caddy отдаёт его по HTTPS — внешний сервис видит корректный TLS-конечный адрес.
Общая черта всех трёх: конфигурация живёт в двух маленьких файлах (Caddyfile и hosts), переносится между машинами за минуту и версионируется в dotfiles. Это редкий случай инфраструктуры, где «как в проде» достигается дешевле, чем «как попало».
Источники: документация Caddy (HTTPS quick start), проект mkcert, RFC 6761 (ссылки в шапке).