HTTP 103 Early Hints — это промежуточный ответ сервера, который приходит до окончательного 200/304 и содержит подсказки в заголовках Link с rel=preload. Браузер, получив такой сигнал, может начать сетевые запросы за критичными ресурсами (CSS, JS, шрифты) раньше, чем увидит HTML-документ. На практике это уменьшает простой сети на критическом пути и ускоряет FCP/LCP, особенно на длинных TTFB и при холодных кэших.
Зачем нам early hints в 2025-м
Исторически для ранней доставки ресурсов пробовали HTTP/2 Server Push. Но push сложнее, легко ошибиться с кэшированием, он снят из браузеров Chromium и не считается устойчивой оптимизацией. Early hints, наоборот, сохраняет управление у браузера: сервер лишь подсказывает, а клиент решает — выгружать ресурс или нет. Это безопаснее с точки зрения перегрева канала и проще для эксплуатации.
Ключевая мысль: early hints — это «мягкое ускорение». Мы не насильно «толкаем» байты, а даём браузеру право начать загрузку, если он считает это уместным. Ошибиться труднее, откат — банально убрать лишние
Link.
Как работает 103 Early Hints (вкратце)
Последовательность выглядит так:
- Клиент отправляет запрос страницы.
- Сервер как можно раньше шлёт промежуточный ответ
HTTP/2 103илиHTTP/1.1 103и один или несколько заголовковLinkсrel=preload. - Браузер начинает грузить эти ресурсы параллельно подготовке основного ответа.
- Приходит финальный ответ (обычно
200 OK), страница продолжает рендер.
Поддержка у современных браузеров есть в Chromium-семействе и Safari; Firefox экспериментировал и может требовать флагов. Даже если конкретный клиент проигнорирует 103, финальные Link: preload в ответе 200 не мешают — они просто не дадут выигрыш настолько рано.

Какие ассеты реально стоит preload-ить
Ресурсы в 103 должны быть «на 100% понадобятся» сразу после старта рендеринга:
- Критический CSS основного бандла:
as=style. - Ключевой JS, без которого не работает above-the-fold:
as=script. - Первичный webfont для заголовков/основного текста:
as=font; type=font/woff2; crossorigin.
Аккуратно с картинками: они огромные и часто лениво подгружаются. Если у вас стабильный hero на первом экране, preload может помочь, но проверяйте метрики — иногда лучше fetchpriority=high в HTML.
Золотое правило: не более 3–6 ссылок в 103. Чем больше подсказок, тем выше шанс лишней конкуренции за TCP-оконце и рост блокировок при медленном канале.
Apache: включаем early hints за 5 минут
Apache умеет генерировать 103 на уровне веб-сервера. Вам понадобится mod_http2 и mod_headers. Проверьте, что HTTP/2 включён для виртуального хоста, и добавьте подсказки с атрибутом early.
# httpd.conf (или в конфиге vhost)
LoadModule http2_module modules/mod_http2.so
LoadModule headers_module modules/mod_headers.so
# Внутри виртуального хоста TLS
<VirtualHost *:443>
ServerName example.com
Protocols h2 http/1.1
# Включаем генерацию 103
H2EarlyHints On
# Список preload через Link, именно для 103
Header add Link "</assets/app.css>; rel=preload; as=style" early
Header add Link "</assets/app.js>; rel=preload; as=script" early
Header add Link "</fonts/Inter.woff2>; rel=preload; as=font; type=font/woff2; crossorigin" early
DocumentRoot "/var/www/html"
</VirtualHost>
Пара замечаний:
earlyговоритmod_headersположить заголовок именно в 103, а не только в финальный ответ.- Если у вас динамика по ассетам (хеш в имени файла), поддерживайте шаблоны через переменные окружения/перегенерацию конфига при деплое.
- Присваивайте корректный
asиtype— браузер использует их для планирования.
Если параллельно настраиваете безопасность, пригодится наш разбор про заголовки: HTTP Security Headers в Nginx и Apache.
Сгенерировать 103 из приложения за Apache
Apache также способен пробросить 103, если его сгенерировало приложение до финального ответа. Это полезно, когда список ассетов зависит от рендеринга.
# PHP (apache2handler). Работает только если фронтенд допускает 103
header('HTTP/1.1 103 Early Hints');
header('Link: </assets/app.css>; rel=preload; as=style', false);
header('Link: </assets/app.js>; rel=preload; as=script', false);
flush();
// Далее обычная отдача HTML
http_response_code(200);
echo '<!doctype html>...';
В php-fpm за прокси поведение зависит от фронтенда: не каждый сервер пробрасывает 103 дальше. Смотрите следующий раздел для Nginx.

Nginx и 103: реалистичные варианты
В чистом Nginx нет штатной директивы, которая «синтезирует» 103 из Link-списка, как это делает Apache. Практически применимы два пути:
- Приложение само отправляет 103 (Node.js, Go, некоторые фреймворки на Ruby/Java), а Nginx пробрасывает ответ клиенту.
- Nginx проксирует на Apache, который формирует 103, а Nginx выполняет роль фронтенда/балансера.
В обоих случаях важно протестировать, что конкретная версия Nginx в вашем окружении действительно пересылает промежуточные ответы 1xx клиенту. Клиентская сторона должна говорить по HTTP/2 или HTTP/3, иначе выгоды не будет.
# Пример reverse proxy Nginx перед Apache или приложением, которое шлёт 103
server {
listen 443 ssl http2;
server_name example.com;
# TLS-настройки опущены
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:8080;
}
}
Если у вас проект на собственном сервере, удобнее всего экспериментировать на VDS: полный доступ к конфигам Nginx/Apache и логам. Для HTTPS сразу подключайте SSL-сертификаты, иначе браузеры не включат HTTP/2/3 и профит от 103 будет минимальным. Для сравнения стеков и подходов также посмотрите материал Nginx vs Apache в 2025.
Генерация 103 на уровне приложения (через Nginx)
Современные рантаймы позволяют отправлять 103 самостоятельно. Два рабочих примера:
// Node.js (18+) — Express-подобный обработчик
res.writeEarlyHints({
Link: '</assets/app.css>; rel=preload; as=style, </assets/app.js>; rel=preload; as=script'
});
res.status(200).send('<!doctype html>...');
// Go (1.19+) — net/http
w.Header().Add("Link", "</assets/app.css>; rel=preload; as=style")
w.Header().Add("Link", "</assets/app.js>; rel=preload; as=script")
w.WriteHeader(103)
if f, ok := w.(http.Flusher); ok { f.Flush() }
// финальный ответ
w.WriteHeader(200)
w.Write([]byte("<!doctype html>..."))
Проверка: видит ли клиент 103
Самый простой способ — через curl и сетевой инспектор браузера.
# Сырой заголовочный обмен с HTTP/2
curl -I --http2 --raw https://example.com/
В корректной реализации вы увидите два блока статуса: сначала HTTP/2 103 с несколькими Link, затем финальный HTTP/2 200. В DevTools в разделе Network откройте запрос документа: Chrome показывает секцию «103 Early Hints» с теми же заголовками.
Как подбирать список Link: preload
Алгоритм, который хорошо себя показал в продакшене:
- Соберите критический путь от навигации до первого значимого рендера (FCP/LCP). Обычно это главный CSS и один-два JS-чанка.
- Проверьте кэшируемость: 103 особенно полезен при холодном кэше и на первой загрузке.
- Добавьте webfont только если он используется на первом экране и без него будет layout shift. Не забудьте
crossorigin. - Не preload-ьте ресурсы, которые могут не понадобиться на этой странице (динамические виджеты ниже фолда).
- Ограничьте объём: суммарный размер preload-ресурсов не должен забивать канал и мешать получению HTML.
В помощь с кэшированием статики — чеклист и примеры в статье Cache-Control и ETag для статики. Для кросс-доменных шрифтов/ассетов может пригодиться настройка CORS: CORS-заголовки в Nginx и Apache.
Метрики: что измерять и как интерпретировать
Целевые индикаторы при включении early hints:
- Снижение FCP/LCP на медленных сетях и при высоком TTFB.
- Уменьшение времени загрузки первого CSS-файла и главного JS-бандла.
- Стабилизация рендеринга: меньше layout shift из-за шрифтов (если preload фонтов корректен).
Проводите A/B: часть трафика без 103, часть — с 103, одинаковые списки Link в 200. Ищите статистически значимое улучшение. Если дельта незаметна — возможно, у вас и так быстрый TTFB или ассеты уже в кэше.
Частые ошибки и подводные камни
- Отсутствует
asили указан неверно. Браузер может проигнорировать preload или ошибиться с приоритетами. - Шрифты без
crossorigin. Для фонтов черезLink: preloadтребуетсяcrossorigin, иначе запрос пойдёт дважды. - Предзагрузка тяжёлых изображений, которые не участвуют в LCP. Это расходует бюджет канала, замедляя доставку HTML.
- Дублирующиеся preload в 103 и в HTML. Само по себе не критично, но поддерживать сложнее; держите источник истины один.
- Слишком много ссылок в 103. Ограничьте до самого необходимого.
Rollback и фичефлаг
Делайте включение 103 управляемым: флаг в конфиге Apache (H2EarlyHints On|Off) или переключатель на уровне приложения. В случае деградации сети/клиентов достаточно убрать секцию с Link early или перестать эмитить 103 из кода. Финальные Link на 200 можно оставить — они не навредят.
Чем early hints отличаются от HTTP/2 Push
Коротко: push пытался предугадать и «запихнуть» ресурс в клиент, часто мимо кэша и приоритетов, что приводило к избыточному трафику; браузеры его убрали. Early hints лишь подсказывают, какие запросы стоит начать раньше, на условиях клиента. Это делает оптимизацию предсказуемой, простой и совместимой с кэшем.
Итоги
HTTP 103 Early Hints — практичный способ ускорить загрузку критичных ассетов без тяжёлой инфраструктуры. Apache позволяет включить 103 буквально несколькими строками, а для Nginx рабочая стратегия — пробрасывать 103, сформированный приложением или бэкендом на Apache. Дисциплина в выборе ресурсов для preload и аккуратные эксперименты с метриками дадут ощутимый выигрыш в реальном UX без риска перегреть канал и усложнить поддержку.


