Современные веб-API (service workers, геолокация, буфер обмена, камера) требуют HTTPS — даже во время разработки. Это руководство описывает, как получить доверенный HTTPS на localhost без предупреждений браузера.
Почему нельзя использовать Let’s Encrypt для localhost
Let’s Encrypt подтверждает владение доменом, проверяя файл на вашем сервере или DNS-запись. localhost — это не настоящий домен, он разрешается в 127.0.0.1 только на вашей машине. Никто не может подтвердить, что вы “владеете” localhost, поэтому ни один публичный удостоверяющий центр не выдаст для него сертификат.
Ваши варианты для HTTPS на localhost:
- mkcert — создаёт локально доверенные сертификаты (рекомендуется)
- Самоподписанный сертификат — работает, но показывает предупреждения браузера
- Сервис туннелирования — ngrok, Cloudflare Tunnel (реальный домен, реальный сертификат)
Вариант 1: mkcert (рекомендуется)
mkcert создаёт локальный удостоверяющий центр на вашей машине, добавляет его в системное хранилище доверия и выпускает сертификаты, подписанные этим удостоверяющим центром. Браузеры доверяют этим сертификатам без предупреждений.
Установка mkcert
# macOS
brew install mkcert
# Linux (требуется libnss3-tools для Firefox)
sudo apt install libnss3-tools
brew install mkcert # или скачайте с GitHub releases
# Windows
choco install mkcert
# или: scoop install mkcert
Настройка локального удостоверяющего центра
mkcert -install
# Вывод: Created a new local CA
# Локальный удостоверяющий центр установлен в системное хранилище доверия
Это добавляет корневой сертификат в системные хранилища доверия и хранилища браузеров. Делается один раз — работает для всех будущих сертификатов.
Генерация сертификатов
# Для localhost
mkcert localhost 127.0.0.1 ::1
# Для пользовательского локального домена
mkcert myapp.test "*.myapp.test" localhost 127.0.0.1
# Вывод:
# Created a new certificate valid for the following names:
# - "localhost"
# - "127.0.0.1"
# - "::1"
# The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem"
Использование с сервером разработки
Node.js/Express:
const https = require('https');
const fs = require('fs');
const app = require('./app');
https.createServer({
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
}, app).listen(3000);
Nginx (локально):
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /path/to/localhost+2.pem;
ssl_certificate_key /path/to/localhost+2-key.pem;
# ...
}
Vite / webpack dev server:
// vite.config.js
import fs from 'fs';
export default {
server: {
https: {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
},
},
};
Предупреждение о безопасности
Никогда не делитесь корневым ключом удостоверяющего центра mkcert (rootCA-key.pem в mkcert -CAROOT). Любой, кто его получит, сможет создавать доверенные сертификаты для любого домена на вашей машине — по сути, возможность локальной атаки типа «человек посередине».
mkcert предназначен только для разработки. Для продакшена используйте Let’s Encrypt.
Вариант 2: Самоподписанный сертификат
Если вы не можете установить mkcert, сгенерируйте самоподписанный сертификат с помощью OpenSSL:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-keyout localhost-key.pem -out localhost-cert.pem \
-days 365 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Недостаток: Браузеры показывают предупреждение безопасности, так как самоподписанные сертификаты не являются доверенными для какого-либо удостоверяющего центра. Вам придётся каждый раз проходить через предупреждение (или добавить исключение).
Вариант 3: Туннелирование (реальный домен + реальный сертификат)
Используйте сервис туннелирования, чтобы открыть доступ к локальному серверу с реальным доменом и сертификатом:
# ngrok
ngrok http 3000
# Выдаёт: https://abc123.ngrok.io
# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000
# Выдаёт: https://random-name.trycloudflare.com
Когда это имеет смысл: тестирование вебхуков, демонстрация коллеге, тестирование обратных вызовов OAuth, которые требуют HTTPS.
mkcert vs самоподписанный vs туннелирование
| mkcert | Самоподписанный | Туннелирование | |
|---|---|---|---|
| Предупреждения браузера | Нет | Да (каждый сеанс) | Нет |
| Сложность настройки | Низкая (одна установка) | Низкая (одна команда) | Низкая |
| Работает офлайн | Да | Да | Нет |
| Производительность | Локальная скорость | Локальная скорость | Сетевая задержка |
| Пользовательские домены | Да (*.test и т. д.) | Да | Назначаются провайдером |
| Лучше всего для | Ежедневной разработки | Разового использования | Вебхуков, демонстраций |
Часто задаваемые вопросы
Можно ли использовать один сертификат mkcert для всей команды?
Не делитесь корневым ключом удостоверяющего центра. Каждый разработчик должен выполнить mkcert -install на своей машине для создания собственного локального удостоверяющего центра. Вы можете делиться сгенерированными сертификатами (файлами .pem), но каждому нужен установленный удостоверяющий центр для их доверия.
Работает ли mkcert с Docker?
Да. Подключите файлы сертификатов в контейнер и укажите их в конфигурации сервера. Самому контейнеру не нужен установленный mkcert — только файлы .pem. На вашей хост-машине (где запускается браузер) должен быть установлен удостоверяющий центр mkcert.
Какой локальный домен использовать?
Используйте .test (например, myapp.test) — он зарезервирован IETF для тестирования и никогда не будет конфликтовать с реальным доменом. Избегайте .dev (принадлежит Google) и .local (используется mDNS). Добавьте запись в /etc/hosts, чтобы направить ваш домен .test на 127.0.0.1.
Как перейти с HTTPS на localhost на продакшен-HTTPS?
Это полностью раздельные вещи. Ваш сертификат для localhost (mkcert/самоподписанный) остаётся на машине разработки. Для продакшена получите настоящий сертификат от Let’s Encrypt через GetHTTPS. Ваш код должен считывать пути к сертификатам из переменных окружения, чтобы переключение сводилось к изменению конфигурации.