# TCP keepalive _Сеть: L4 и выше · LinuxLab Knowledge Base_ **TL;DR:** Keepalive шлёт пробы по простаивающему TCP-соединению чтобы поймать мёртвый peer (NAT-таймаут, упавший хост). Дефолт Linux: 7200s простоя, 75s между пробами, 9 проб. Включается через setsockopt(SO_KEEPALIVE). ## Зачем keepalive TCP без трафика не знает, жив ли peer. Если клиент молча выдернул шнур или NAT закрыл маппинг - сервер увидит это только при попытке послать данные. На long-lived соединениях (БД-пулы, WebSocket'ы, очереди) это плохо: тред занят «зомби»-соединением. **Keepalive** - системный механизм: после N секунд тишины ядро шлёт «пробный» ACK с устаревшим sequence-number. Если peer жив - вернёт ACK; если мёртв - ничего, и после M проб ядро закрывает сокет с ETIMEDOUT. ## Три тюнинга на хост | sysctl | дефолт | что | |--------|--------|-----| | `net.ipv4.tcp_keepalive_time` | 7200 (2 ч) | сколько секунд тишины до первой пробы | | `net.ipv4.tcp_keepalive_intvl` | 75 | интервал между пробами | | `net.ipv4.tcp_keepalive_probes` | 9 | сколько проб до объявления мёртвым | Дефолт = 2 часа простоя + 9×75с = ~2 часа 11 минут до закрытия. В 99% сценариев это безумно долго. ## Включить keepalive в приложении Параметр сокета `SO_KEEPALIVE` - **по дефолту выключен**. Нужно явно: ```python import socket s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # Per-socket override (Linux): s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) # 60s простоя s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) # 10s между пробами s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3) # 3 пробы ``` Получится: 60 + 3×10 = 90 секунд от тишины до закрытия. ## Когда нужно тюнить - **БД-пулы** (PostgreSQL/MySQL): дефолт 2 часа значит что после рестарта БД пул держит «мёртвые» сокеты до первого SQL-запроса. Поставить keepalive 30-60 секунд. - **WebSocket / gRPC** с прокси посередине: NAT/LB закрывают idle обычно через 5-10 минут. Keepalive каждые 30-60 сек спасает. - **VPN / SSH туннели**: то же. - **API behind cloud LB**: AWS NLB закрывает idle через 350с по дефолту. ## Keepalive vs application ping | Подход | Плюсы | Минусы | |--------|-------|--------| | TCP keepalive | бесплатно, в ядре | не проверяет, что *приложение* живо - только сокет; не работает через прокси, проксирующий L7 | | Application ping | проверяет всю цепочку до handler'а | надо реализовать, лишний traffic | Для [[websocket|WebSocket]] правильно делать обе - keepalive ловит оборванный TCP, application-ping (frame opcode 0x9) ловит зависший сервер. ## Что видно в tcpdump Keepalive-проба = пакет с `seq = current_seq - 1`, без payload, флаг ACK. В tcpdump смотри пустой ACK через интервал tcp_keepalive_intvl от предыдущего трафика. ``` IP 10.0.0.1.443 > 10.0.0.5.34521: Flags [.], ack 100, win 1024, length 0 ``` ## Заметки - Keepalive не сохраняет соединение «живым» в смысле NAT - он шлёт пакеты, и NAT ровно поэтому не таймаутится. То есть **меньше `tcp_keepalive_time`, чем NAT-timeout** - и NAT не закроет. - На современных Linux есть **`TCP_USER_TIMEOUT`** - альтернатива: «закрыть соединение если за N миллисекунд нет ACK на отправленные данные». Часто полезнее keepalive'а, потому что работает и под нагрузкой. ## Когда что-то пошло не так - **Соединение «висит»** через 5 минут idle и не закрывается - keepalive выключен, либо tcp_keepalive_time > NAT-timeout - **Слишком частые пробы шумят** - tcp_keepalive_intvl слишком мал, либо TCP_KEEPIDLE = 5 секунд (избыточно) - **Соединение закрылось за 5 минут хотя трафик идёт** - не keepalive, смотри NAT/LB конфиг (idle timeout != keepalive) - **`error: ETIMEDOUT`** на send() - keepalive отработал, peer мёртв ## Команды ```bash sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes ``` Текущие глобальные дефолты keepalive ```bash sudo sysctl -w net.ipv4.tcp_keepalive_time=60 ``` Снизить простой до 60с глобально (нужен ребут или sysctl persist) ```bash ss -tnoH state established | head ``` Колонка timer показывает keepalive: 'keepalive(45sec,...)' если включён ```bash ss -tnoeH | awk '/keepalive/ {print}' ``` Только сокеты с активным keepalive timer'ом ## См. также - [TCP three-way handshake](/kb/tcp-handshake.md) - [TCP states (LISTEN, ESTABLISHED, TIME_WAIT)](/kb/tcp-states.md) - [Conntrack - память Linux о всех сетевых соединениях](/kb/conntrack.md) - [Порт - как несколько сервисов делят один IP](/kb/port.md) - [iperf3 - измерение bandwidth](/kb/cmd-iperf3.md)