TCP Fast Open (TFO) — это RFC 7413, который вышел в 2014-м, и с тех пор его то хвалят, то ругают. Сторонники говорят «экономит один RTT на handshake, must have». Противники — «в реальной сети промежуточные коробки рвут пакеты с TFO-cookie, лучше не включать». Я решил поставить вопрос на эксперимент: включил TFO на нескольких нодах, оставил без TFO на парных, и сравнил полугодовые метрики. Делюсь результатами.
Что такое TFO в одну строчку
Обычный TCP handshake — три пакета: SYN, SYN-ACK, ACK. Данные клиент может слать только после ACK. То есть один полный RTT тратится впустую.
TFO позволяет клиенту слать данные уже в SYN-пакете. Сервер может ответить SYN-ACK с данными ответа. Экономия — один RTT на каждое новое соединение. Для HTTP-запроса с RTT 100 мс это уже 100 мс минус.
Работает только при наличии TFO-cookie, которое клиент получает при первом коннекте к серверу и переиспользует при следующих.
Как включить
На сервере и клиенте:
sysctl -w net.ipv4.tcp_fastopen=3
Битовая маска: 1 — клиент, 2 — сервер, 3 — оба. Значение 3 — обычно то, что нужно.
Для приложений требуется поддержка на уровне socket-опций (TCP_FASTOPEN). nginx это умеет с версии 1.5.8:
server {
listen 443 ssl http2 fastopen=256;
...
}
Цифра 256 — это размер очереди TFO-запросов. Если у вас 10k RPS — ставьте побольше, например, 4096.
Проверка, что включился
На сервере:
ss -tlni '( sport = :443 )' | head
# TCP_FASTOPEN qlen 256
На стороне клиента — tcpdump покажет TFO option в SYN:
tcpdump -i any -nn 'tcp[tcpflags] & tcp-syn != 0' -v
В выводе будет Fast Open в опциях.
Что показали измерения
Тестовые ноды — четыре HTTP-балансировщика с nginx 1.27, два в Европе, два в Сингапуре. Идентичная конфигурация, идентичный бэкенд. На двух (по одной в каждом регионе) включил TFO=3. На двух — оставил дефолт.
Метрики снимал через RUM (real user monitoring) с фронтовых сайтов. Самое важное — TTFB (time to first byte) клиентов.
Европа (короткий RTT, в среднем 30-50 мс)
p50 TTFB без TFO: 142 мс
p50 TTFB с TFO: 138 мс
p99 TTFB без TFO: 720 мс
p99 TTFB с TFO: 705 мс
Разница — едва заметная. На p50 — около 4 мс, что в пределах шума.
Азия (длинный RTT, 200-300 мс)
p50 TTFB без TFO: 680 мс
p50 TTFB с TFO: 540 мс
p99 TTFB без TFO: 2.4 с
p99 TTFB с TFO: 1.85 с
А вот тут разница уже ощутимая. На длинных RTT экономия одного round-trip даёт 20% улучшения p50 и заметное сокращение p99.
Почему так?
TFO экономит RTT только на новых соединениях. Если у вас HTTP/2 (а в 2025-м это уже стандарт) — одно соединение переиспользуется для всех запросов в рамках страницы. То есть TFO даёт выигрыш только на первом запросе.
На коротких RTT этот один RTT — копейки. На длинных — заметно. Поэтому для CDN-узлов в дальних регионах TFO имеет смысл, для локальных балансёров — почти нет.
Грабли, которые встретил
Промежуточные коробки
В нескольких сетях (особенно у российских провайдеров и в азиатских мобильных сетях) TFO-пакеты молча дропаются. Симптом — клиент шлёт SYN с TFO data, не получает ответ, после нескольких ретрансмиссий ядро откатывается на обычный TCP. Это занимает 3-10 секунд. То есть на «плохой сети» TFO не ускоряет, а замедляет первое соединение.
К счастью, ядро Linux помнит, для каких dest-IP TFO не работает, и автоматически перестаёт пробовать его туда же. Но первый раз — больно. Видно в nstat:
nstat -az | grep -i fastopen
TcpExtTCPFastOpenBlackhole — это как раз счётчик случаев, когда TFO не сработал.
Symantec, McAfee и прочие firewall’ы
Корпоративные TLS-инспекторы часто не понимают TFO и режут трафик. Для публичного сайта это значит, что часть клиентов из корпоративных сетей не сможет открыть сайт вообще. Это, наверное, главный аргумент против TFO на B2C-сайтах.
Замер «улучшения» в локалке
Если будете тестировать TFO на localhost или на ноде в той же стойке — никакого ускорения не увидите. RTT 0.1 мс × 1 = 0.1 мс выигрыша. Тестируйте на реальных дальних клиентах.
TFO и QUIC
TFO применим только к TCP. QUIC поверх UDP имеет свой 0-RTT mode, который концептуально похож. Если ваш фронт уже на HTTP/3 (QUIC) — TFO для этого трафика не нужен. Но для HTTP/1.1 и HTTP/2 запросов остаётся актуальным.
Можно ли сломать TFO
Да. Если на одной из сторон отключён, TFO не работает. Если у клиента сменился исходящий IP (мобильник перешёл на другую соту с другим NAT) — старый TFO-cookie невалиден, и первый раз будет обычный handshake.
Также есть tcp_fastopen_blackhole_timeout_sec — таймаут, на сколько ядро запоминает «эта пара src-dst не поддерживает TFO». Дефолт — 3600 секунд. Если у вас сеть нестабильная — можно увеличить.
Итог
TFO имеет смысл включать на серверах, которые обслуживают дальних клиентов с RTT >100 мс. На локальных нагрузках и за корпоративными firewall’ами — выигрыш минимален, а вреда от «странных дропов» больше. Если ваш фронт уже на QUIC — TFO для него не нужен. Включайте tcp_fastopen=3 на нодах, где это даёт +1, и не парьтесь там, где разница в пределах статистической погрешности.