Два інструменти, одна задача: ваш dev-сервер має бути доступним через HTTPS. mkcert дає локально довірений TLS-сертифікат, тож https://localhost:3000 просто працює. Тунель дає публічний HTTPS-URL зі справжнім сертифікатом від справжнього CA. Вони не конкуренти — розв’язують різні задачі, — але люди постійно ставляться до них як до конкурентів.
Коротка відповідь
- Потрібен HTTPS лише на власній машині, для власного браузера? Беріть mkcert.
- Потрібно, щоб зовнішній сервіс (Stripe, GitHub, Auth0, телефон) дістався dev-сервера? Беріть тунель.
- І те, і те? Запустіть обидва. Вони не конфліктують.
Це вся стаття в трьох пунктах. Решта — лише чому.
mkcert: локально довірені сертифікати за 30 секунд
mkcert встановлює локальний центр сертифікації у сховище довіри вашої ОС і випускає з нього сертифікати. Браузер бачить сертифікат як довірений, бо CA довірений. Жодних попереджень з’єднання не захищене, жодних прапорів --ignore-certificate-errors, жодної саморобної self-signed мороки.
# один раз на машину
mkcert -install
# один раз на проєкт
mkcert localhost 127.0.0.1
# тепер у вас localhost.pem і localhost-key.pem
Підключіть їх до dev-сервера (Vite, Next.js, Express, Django runserver_plus — усі підтримують TLS-аргументи), і у вас https://localhost:3000 зі справжнім сертифікатом.
Підступ: цьому CA довіряє лише ваша машина. Браузер колеги попередить. Телефон не довіриться без ручного встановлення кореня CA.
Тунель: справжній HTTPS, справжній публічний URL
localhost-тунель перенаправляє трафік із публічного HTTPS-URL на ваш локальний порт. Сертифікат випущений справжнім CA (Let's Encrypt тощо) і довірений кожним браузером на кожному пристрої.
npx portpreview 3000
Тунель виконує термінування TLS на хмарному шлюзі. Локальний сервер може лишатися на звичайному HTTP — публічний URL це HTTPS, і саме його торкається кожен зовнішній сервіс.
Пліч-о-пліч
| Потреба | mkcert | Тунель |
|---|---|---|
| HTTPS у локальному браузері | Так | Так (через публічний URL) |
| Тест service worker / secure cookie у браузері | Так | Так |
| Зовнішній провайдер може вас дістати | Ні | Так |
| Телефон у руці може вас дістати | Ні (без встановлення CA) | Так |
| OAuth-провайдер приймає URL | Іноді (Google: так; багато: ні) | Так |
| Stripe / GitHub / Twilio можуть слати вебхук | Ні | Так |
| Час налаштування | 30 секунд на машину | Одна команда на сесію |
| Працює офлайн | Так | Ні |
| Переживає режим польоту | Так | Ні |
Де люди помиляються
Намагаються використати mkcert для тестування вебхуків
Stripe не може довіряти локальному CA вашої машини. Байдуже, наскільки довіреним виглядає сертифікат у браузері, — Stripe в іншій мережі. Для будь-якого вхідного трафіку з публічного інтернету потрібен тунель.
Використовують тунель для сольного HTTPS лише в браузері
Якщо треба лише, щоб запрацювали service worker або встановилася secure cookie у власному браузері, mkcert швидший і працює офлайн. Не палить сесію тунелю на те, що розв’язує файл сертифіката.
Обирають один інструмент і проштовхують крізь нього інший кейс
Запускати обидва — нормально. Більшість нашого налаштування використовує mkcert для щоденної роботи з боку браузера й тунель, коли тестуємо вебхуки чи OAuth-колбеки. Вони співіснують у package.json без конфлікту.
А як щодо Caddy чи nginx?
Так, можна запустити Caddy з автоматичним HTTPS перед dev-сервером, і це теж працює — по суті це «mkcert із зайвими кроками й зворотним проксі». Для більшості локальної розробки mkcert простіший. Для складнішої маршрутизації з кількома локальними сервісами Caddy виправдовує себе.
Наше реальне налаштування
В одному репозиторії, де ми працюємо, є mkcert у dev-скрипті для HTTPS на боці localhost і окремий npm-скрипт tunnel, що запускає portpreview, коли комусь потрібні вебхуки чи тестування на мобільних. URL тунелю передається через env-змінну, тож OAuth redirect URI легко міняти. На під’єднання пішло 20 хвилин, і відтоді ми не думали про локальний TLS.
Про налаштування тунелю під OAuth див. як тестувати OAuth-колбеки локально. Щоб ділитися прев’ю з колегами чи дизайнерами — поділитися локальним dev-сервером. Приєднайтеся до списку очікування PortPreview заради тунельної частини цього налаштування.