Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Локальный HTTPS и reverse-proxy: Caddy, mkcert, домены .test

HTTPS на localhost: Caddy, mkcert и нормальные домены для локальной разработки

Как разработчику получить доверенный HTTPS локально: корневой сертификат mkcert, reverse-proxy Caddy, домены .test, проксирование API и типовые ошибки самоподписанных сертификатов.

Прямой ответ

Современные браузерные 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 не обойтись

  1. Service workers и PWA — требуют secure origin; localhost считается таковым, но доменное имя с HTTP — уже нет.
  2. OAuth-редиректы на кастомные домены, cookie с Secure/SameSite=None, cross-site-сценарии между app.test и api.app.test — корректно тестируются только по HTTPS с настоящими доменами.
  3. 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 (ссылки в шапке).