https и самоподписанные сертификаты

Назначение HTTPS в локальной разработке

Webpack Dev Server поддерживает запуск локального сервера по протоколу HTTPS. Это особенно важно в современных frontend-приложениях, поскольку многие браузерные API работают только в защищённом контексте.

К таким API относятся:

  • Service Workers
  • Push API
  • WebAuthn
  • Clipboard API
  • Geolocation API
  • HTTP/2
  • Secure Cookies
  • Некоторые возможности PWA

При обычном HTTP часть функциональности либо полностью блокируется браузером, либо работает с ограничениями.

Webpack Dev Server позволяет запускать локальный сервер через TLS/SSL и использовать сертификаты аналогично production-среде.


Базовое включение HTTPS

В Webpack Dev Server HTTPS активируется через параметр server.

Пример для Webpack 5:

module.exports = {
    devServer: {
        server: 'https'
    }
};

После запуска dev server приложение будет доступно по адресу:

https://localhost:8080

Старый синтаксис https: true

В старых конфигурациях использовалась запись:

devServer: {
    https: true
}

В Webpack 5 предпочтительным считается новый синтаксис через server.

Современный вариант:

devServer: {
    server: 'https'
}

Как работает HTTPS внутри webpack-dev-server

Webpack Dev Server создаёт локальный HTTPS-сервер поверх Node.js TLS API.

При запуске:

  1. создаётся TLS-соединение;
  2. сервер использует сертификат;
  3. браузер проверяет доверие к сертификату;
  4. устанавливается защищённый канал.

Если сертификат самоподписанный, браузер обычно показывает предупреждение безопасности.


Самоподписанные сертификаты

Что такое self-signed certificate

Самоподписанный сертификат — сертификат, подписанный не центром сертификации (CA), а самим владельцем.

Для локальной разработки это нормальная практика.

Такие сертификаты:

  • бесплатны;
  • быстро создаются;
  • подходят для localhost;
  • позволяют тестировать HTTPS-функциональность.

Но браузер не доверяет таким сертификатам автоматически.


Автоматический сертификат webpack-dev-server

Если указать:

devServer: {
    server: 'https'
}

Webpack Dev Server способен автоматически создать временный сертификат.

Проблемы такого подхода:

  • браузер показывает предупреждение;
  • сертификат не считается доверенным;
  • возможны ошибки WebSocket/HMR;
  • некоторые API работают нестабильно.

Для полноценной разработки обычно создают собственный локальный сертификат.


Генерация самоподписанного сертификата через OpenSSL

Создание приватного ключа

openssl genrsa -out localhost.key 2048

Создание сертификата

openssl req -new -x509 \
    -key localhost.key \
    -out localhost.crt \
    -days 365

В процессе OpenSSL запросит параметры:

Country Name
State
Locality
Organization
Common Name

Для локальной разработки особенно важен параметр:

Common Name

Обычно указывается:

localhost

Структура сертификатов

После генерации появляются файлы:

certs/
├── localhost.crt
└── localhost.key
  • .key — приватный ключ;
  • .crt — публичный сертификат.

Приватный ключ нельзя публиковать в репозитории.


Подключение сертификатов в Webpack

Конфигурация HTTPS-сервера

const fs = require('fs');
const path = require('path');

module.exports = {
    devServer: {
        server: {
            type: 'https',
            options: {
                key: fs.readFileSync(
                    path.resolve(__dirname, 'certs/localhost.key')
                ),

                cert: fs.readFileSync(
                    path.resolve(__dirname, 'certs/localhost.crt')
                )
            }
        }
    }
};

Параметры server.options

Webpack передаёт объект options непосредственно в HTTPS-сервер Node.js.

Можно использовать:

server: {
    type: 'https',
    options: {
        key,
        cert,
        ca,
        passphrase,
        requestCert
    }
}

Использование .pem

Вместо .crt и .key часто используются .pem-файлы.

Пример:

server: {
    type: 'https',
    options: {
        key: fs.readFileSync('./certs/key.pem'),
        cert: fs.readFileSync('./certs/cert.pem')
    }
}

Проверка HTTPS

После запуска:

npm run dev

или:

webpack serve

сервер становится доступен:

https://localhost:8080

При первом открытии браузер может показать:

Your connection is not private

Это связано с отсутствием доверия к сертификату.


Добавление сертификата в доверенные

Windows

Сертификат .crt импортируется в:

Trusted Root Certification Authorities

Через:

certmgr.msc

macOS

Сертификат импортируется в:

Keychain Access

После импорта необходимо установить:

Always Trust

Linux

В Linux доверенные сертификаты зависят от дистрибутива.

Для Ubuntu:

sudo cp localhost.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

mkcert

Назначение mkcert

mkcert — один из самых удобных инструментов для локальных HTTPS-сертификатов.

Преимущества:

  • автоматическое создание локального CA;
  • браузеры доверяют сертификатам;
  • минимальная настройка;
  • корректная работа HMR и WebSocket.

Установка mkcert

macOS

brew install mkcert

Windows

choco install mkcert

Linux

sudo apt install mkcert

Создание локального CA

mkcert -install

Команда создаёт локальный root certificate authority и добавляет его в систему.


Генерация сертификата для localhost

mkcert localhost

Создаются файлы:

localhost.pem
localhost-key.pem

Подключение mkcert в Webpack

const fs = require('fs');

module.exports = {
    devServer: {
        server: {
            type: 'https',
            options: {
                key: fs.readFileSync('./localhost-key.pem'),
                cert: fs.readFileSync('./localhost.pem')
            }
        }
    }
};

HTTPS и Hot Module Replacement

Особенности HMR через HTTPS

HMR использует WebSocket-соединение.

При HTTPS браузер требует:

  • защищённый WebSocket (wss://);
  • корректный сертификат;
  • отсутствие TLS-ошибок.

Если сертификат недоверенный, возможны ошибки:

WebSocket connection failed

или:

ERR_CERT_AUTHORITY_INVALID

Настройка WebSocket URL

Иногда требуется явная настройка клиента:

devServer: {
    client: {
        webSocketURL: {
            hostname: 'localhost',
            port: 8080,
            protocol: 'wss'
        }
    }
}

Это особенно важно:

  • при reverse proxy;
  • Docker;
  • удалённой разработке;
  • нестандартных доменах.

HTTPS и reverse proxy

Webpack Dev Server часто работает за Nginx.

Схема:

Browser
   ↓ HTTPS
Nginx
   ↓ HTTP
Webpack Dev Server

В этом случае HTTPS может завершаться на proxy-сервере.


HTTPS напрямую в Webpack

Иногда HTTPS нужен непосредственно внутри webpack-dev-server:

Browser
   ↓ HTTPS
Webpack Dev Server

Такой подход удобен:

  • для локальной разработки;
  • PWA;
  • мобильного тестирования;
  • WebSocket;
  • Service Workers.

Использование пользовательского hostname

По умолчанию используется:

localhost

Но можно указать собственный hostname:

devServer: {
    host: 'local.project.test',

    server: {
        type: 'https',
        options: {
            key,
            cert
        }
    }
}

Сертификаты для кастомного домена

Сертификат должен совпадать с hostname.

Например:

local.project.test

Если сертификат выпущен для localhost, браузер выдаст ошибку:

NET::ERR_CERT_COMMON_NAME_INVALID

Subject Alternative Name (SAN)

Современные браузеры используют SAN вместо Common Name.

При генерации сертификатов необходимо указывать:

DNS:localhost
DNS:local.project.test

Без SAN сертификат может считаться невалидным.


HTTPS и Docker

При использовании Docker сертификаты обычно монтируются в контейнер:

volumes:
  - ./certs:/app/certs

Webpack читает сертификаты из контейнера:

fs.readFileSync('/app/certs/localhost.key')

HTTPS и WSL

В WSL возможны проблемы:

  • сертификат доверен Linux, но не Windows;
  • браузер Windows не доверяет WSL CA;
  • HMR ломается из-за TLS.

Обычно сертификат необходимо импортировать и в Windows.


HTTP/2

Webpack Dev Server способен работать через HTTP/2.

Пример:

devServer: {
    server: {
        type: 'https'
    }
}

Node.js автоматически поддерживает ALPN negotiation.

HTTP/2 даёт:

  • multiplexing;
  • уменьшение latency;
  • более эффективную передачу ресурсов.

HTTPS и secure cookies

Некоторые cookie работают только через HTTPS:

Set-Cookie: session=123; Secure

Без HTTPS браузер игнорирует такие cookie.

Локальный HTTPS позволяет тестировать production-поведение авторизации.


HTTPS и Service Workers

Service Worker требует secure context.

Работают только:

https://

или:

http://localhost

Но полноценное тестирование PWA обычно выполняется именно через HTTPS.


HTTPS и CORS

При HTTPS часто появляются смешанные схемы:

https://frontend.local
http://api.local

Браузер блокирует такие запросы как mixed content.

Необходимо:

  • использовать HTTPS для API;
  • либо proxy внутри webpack-dev-server.

Proxy и HTTPS

Пример proxy:

devServer: {
    proxy: {
        '/api': {
            target: 'https://backend.local',
            secure: false
        }
    }
}

Параметр secure: false

Опция:

secure: false

отключает проверку SSL-сертификата backend-сервера.

Полезно для локальных self-signed сертификатов.


Mixed Content

Типичная ошибка:

Mixed Content: The page was loaded over HTTPS,
but requested an insecure resource

Причины:

  • HTTP API;
  • HTTP изображения;
  • HTTP шрифты;
  • HTTP WebSocket.

Все ресурсы должны использовать HTTPS.


Проверка сертификата через браузер

В браузере можно просмотреть:

  • issuer;
  • validity;
  • SAN;
  • fingerprint;
  • цепочку сертификатов.

Это помогает диагностировать ошибки TLS.


Типичные ошибки HTTPS в Webpack

ERR_CERT_AUTHORITY_INVALID

Браузер не доверяет сертификату.


ERR_CERT_COMMON_NAME_INVALID

Hostname не совпадает с сертификатом.


WebSocket disconnected

Проблемы TLS или wss://.


NET::ERR_SSL_PROTOCOL_ERROR

Некорректная конфигурация TLS.


EPROTO

Ошибка TLS-рукопожатия в Node.js.


Хранение сертификатов в проекте

Обычно используется структура:

project/
├── certs/
│   ├── localhost.key
│   └── localhost.crt
├── webpack.config.js
└── package.json

Исключение сертификатов из Git

.gitignore:

certs/*.key
certs/*.pem
certs/*.crt

Особенно важно исключать приватные ключи.


Использование переменных окружения

Пути к сертификатам можно задавать через .env.

Пример:

SSL_KEY=./certs/dev.key
SSL_CERT=./certs/dev.crt

Конфигурация:

const fs = require('fs');

server: {
    type: 'https',
    options: {
        key: fs.readFileSync(process.env.SSL_KEY),
        cert: fs.readFileSync(process.env.SSL_CERT)
    }
}

HTTPS в monorepo

В monorepo сертификаты часто выносятся в общий каталог:

tools/certs/

Это позволяет использовать один локальный CA для нескольких приложений.


Локальные wildcard-сертификаты

Иногда создаются сертификаты:

*.local.test

Это удобно для микрофронтендов и мультидоменной разработки.


HTTPS и производительность

TLS добавляет:

  • handshake;
  • шифрование;
  • проверку сертификата.

Но для локальной разработки нагрузка обычно незначительна.


Рекомендованный подход для современной разработки

Наиболее стабильная схема:

  1. mkcert;
  2. доверенный локальный CA;
  3. HTTPS в webpack-dev-server;
  4. корректный wss://;
  5. единый HTTPS для frontend и API.

Такой подход максимально близок к production-среде и снижает количество проблем при деплое.