{ "index": 24, "slug": "editorial-2027-05-practice-http-tls-guide", "title": "HTTP и TLS: как найти границу ошибки до того, как менять код", "excerpt": "404, 503 и ошибка сертификата возникают на разных этапах запроса. Разбираем их по наблюдаемым признакам, проверяем локальным примером и не превращаем retry или --insecure в случайное исправление.", "contentHtml": "
Браузер показывает ошибку, а команда сразу меняет timeout, маршрут или сертификат. Через час выясняется, что запрос вообще не дошёл до приложения. В другом случае приложение вернуло 404, но инженер ищет проблему в TLS. Цена такой ошибки — лишний rollout, потерянное время и риск сломать рабочий путь, пытаясь исправить не тот слой.
Тезис: сначала нужно определить первый подтверждённый этап отказа. До HTTP находятся DNS, TCP и TLS. После успешного TLS появляются метод, URI, заголовки и статус HTTP. Если перепутать границу, проверка не отвечает на вопрос и создаёт ложное ощущение прогресса.
\nУ HTTPS-запроса есть последовательность. Клиент разрешает имя, открывает TCP-соединение, проводит TLS-рукопожатие, отправляет HTTP-сообщение и читает ответ. Посредник может завершить запрос на любом шаге. Поэтому текст ошибки важнее цвета страницы: ERR_TLS_CERT_ALTNAME_INVALID ещё не является HTTP-ответом, а 404 означает, что HTTP-обмен уже состоялся.
Уровень ошибки задаёт набор допустимых проверок. Заголовок Host не исправит сертификат, если TLS-клиент не доверяет имени. Увеличение timeout не создаст отсутствующий маршрут. Повтор POST не становится безопасным только потому, что сервер вернул 503.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
ERR_TLS_CERT_ALTNAME_INVALID | Имя в URL не совпадает с SAN | Сверить hostname, SAN и адрес | Исправить имя, сертификат или vhost |
404 Not Found | URI или метод не попал в маршрут | Проверить известный endpoint и лог маршрута | Исправить путь, метод или route config |
503 Service Unavailable | Обработчик или зависимость недоступны | Проверить Retry-After и upstream-логи | Устранить недоступность; retry ограничить контрактом |
| Timeout без статуса | Неизвестен этап задержки | Разделить connect и read timeout | Найти этап, затем менять лимит |
404 — это статус HTTP-ответа. Он не доказывает, что ответ сформировало origin-приложение: его мог вернуть reverse proxy или другой посредник. Сохраняйте метод, нормализованный путь, статус, request ID и безопасный набор заголовков. Полные Cookie, Authorization и чувствительные query-параметры в запись не нужны.
503 сообщает о временной невозможности обработать запрос. Заголовок Retry-After может дать ориентир, но не гарантирует безопасность повтора. Для чтения задайте ограниченный retry с общим deadline. Для записи сначала проверьте идемпотентность и правило дедупликации. Иначе потерянный ответ после успешной записи превратится в дубль.
Отрицательный путь обязателен. Если метод меняет деньги, заказ, подписку или другой ресурс, а сервер не принимает ключ операции и не описывает повтор, клиент должен остановиться. Автоматический retry в таком месте скрывает неопределённый результат.
\nTLS защищает канал и связывает его с именем узла. Клиент сравнивает имя назначения с именами в Subject Alternative Name сертификата. Если URL содержит старый alias, IP-адрес или имя другого виртуального хоста, проверка может завершиться до отправки HTTP-запроса. Тогда у приложения нет статуса и тела для анализа.
\nПроверяйте три свойства: имя, цепочку доверия и срок действия. Общий текст certificate error скрывает различия между ними. Не подменяйте проверку флагом --insecure. Он может показать, что endpoint отвечает без валидации сертификата, но не исправляет доверие и не доказывает безопасность соединения.
Пример ниже учебный. Он запускает только локальный HTTP-сервер, не обращается к внешней сети и не моделирует TLS. Его задача — показать разницу между известным маршрутом и отсутствующим URI. В production этот код не заменяет proxy, сертификат, health-check или журнал приложения.
\nimport { createServer } from 'node:http'; const server = createServer((req, res) => { if (req.method === 'GET' && req.url === '/health') { res.writeHead(200, { 'content-type': 'text/plain' }); res.end('ok'); return; } res.writeHead(404, { 'content-type': 'text/plain' }); res.end('missing'); }); server.listen({ port: 0, host: '127.0.0.1' }, async () => { const { port } = server.address(); const response = await fetch('http://127.0.0.1:' + port + '/missing'); console.log(response.status, await response.text()); server.close(); });\nЗапуск node check-http.mjs в этом учебном случае печатает 404 missing. Если заменить путь на /health, получится 200 ok. Мы проверяем и статус, и тело. Один только текст страницы не показывает, какой контракт нарушен.
Отправьте POST /health и получите 404: маршрут принимает только GET. Это не проблема TLS и не причина увеличивать timeout. Сначала решите, должен ли такой метод существовать в контракте.
Allow, Location, Retry-After, тип тела и идентификатор ответа.HTTP-статус не раскрывает автоматически путь внутри прокси или состояние upstream. Заголовок Server не является доказательством источника ответа. DNS-ответ не доказывает наличие нужного маршрута. Для вывода о реальной инфраструктуре нужны согласованные логи, сетевые данные и разрешённый доступ.
Локальный пример не проверяет CDN, балансировщик, корпоративный proxy, реальную цепочку сертификатов, рестарт процесса или запись в базе. Он показывает только границу между URI, методом и ответом локального HTTP-сервера. Не переносите его упрощённое поведение в production без явного контракта и тестов.
\nЕсли TLS не проходит, не ищите заголовки приложения. Если TLS проходит, но статус равен 404, ищите маршрут и метод. Если пришёл 503, определяйте доступность обработчика и безопасность повтора. Если нет статуса, сначала найдите этап timeout.
Диагностика готова, если для одного hostname можно воспроизвести успешный HTTPS-запрос, ошибку проверки имени сертификата, известный 404 и временный 503. Для каждого случая запись содержит этап, метод, путь, статус или TLS-ошибку, безопасный request ID и одно действие. Для записи с неопределённым результатом retry остановлен или защищён идемпотентным контрактом.
Проверяемый результат — не «ошибка исчезла», а совпадение наблюдения с уровнем проверки. Повторите запрос после изменения одного условия. Если причина и новый результат не различаются по логам или команде, ремонт ещё не доказан.
\n