nf_conntrack: table full, dropping packet — это та строчка, которая обычно появляется в dmesg в самый неподходящий момент. Сначала падает один сервис, потом второй, потом всё ложится, а вы сидите и думаете «почему сеть пропала». Я этих сообщений насмотрелся достаточно, чтобы написать памятку.

Что такое conntrack

Conntrack — это модуль ядра, который отслеживает состояние сетевых соединений. Без него не работают: NAT, MASQUERADE, REDIRECT, stateful firewall (-m state --state ESTABLISHED,RELATED), connection tracking в iptables/nftables.

Если вы делаете SNAT/DNAT — conntrack у вас работает, даже если вы про него не знаете.

Если у вас просто файрвол через -m state — он тоже работает.

Если у вас ни SNAT, ни stateful-правил, и вы ставите -j NOTRACK на трафик — conntrack не активен, и вся эта статья вам не нужна. Но таких систем меньшинство.

Симптомы переполнения

nf_conntrack: table full, dropping packet

И параллельно — таймауты соединений, наугад. Не у всех клиентов, не на всех сервисах — кому повезёт. Это потому что новые соединения, которые попадают на этап «надо создать запись в conntrack», просто отбрасываются.

Проверить текущее заполнение:

cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

Если первое подошло к второму на 70-80% — надо реагировать.

Базовый тюнинг

# /etc/sysctl.d/99-conntrack.conf
net.netfilter.nf_conntrack_max = 2097152
net.netfilter.nf_conntrack_tcp_timeout_established = 7200
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 30
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 120
net.netfilter.nf_conntrack_generic_timeout = 120

nf_conntrack_max — главный параметр. 2 миллиона записей займут примерно 600-800 МБ памяти (≈300 байт на запись). Не надо ставить 10 миллионов «на всякий случай», если у вас 4 ГБ RAM.

tcp_timeout_established=7200 — это два часа. Дефолт — 432000 (5 суток!). За 5 суток у вас в таблице накопится мусор от давно мёртвых соединений.

Хеш-таблица

nf_conntrack_max без правильной hashsize — это половина решения. Хеш-таблица определяет, как быстро ищется запись.

Стандартное правило: hashsize = nf_conntrack_max / 4 (для cgroup v1) или / 2 (для cgroup v2). Точнее — это коэффициент nf_conntrack_buckets.

# При загрузке модуля
echo 'options nf_conntrack hashsize=524288' > /etc/modprobe.d/nf_conntrack.conf

Или на горячую:

echo 524288 > /sys/module/nf_conntrack/parameters/hashsize

Если хеш-таблица слишком маленькая — на каждый бакет приходится много записей, поиск замедляется, и под нагрузкой вы получаете не дропы, а просто высокий CPU в softirq.

Как считать правильный размер

Базовая прикидка:

  • На каждое активное соединение — 1 запись
  • На каждый TIME_WAIT — 1 запись
  • На каждый UDP-flow за последние udp_timeout секунд — 1 запись

Если у вас на пиковой нагрузке 50k активных HTTP, плюс 200k TIME_WAIT, плюс 10k UDP — это 260k. Берите с запасом 4x — 1 миллион. Это даст и буфер на спайки, и место под DDoS-фон.

Что НЕ помогает

Дефолтные «советы из интернета» типа «уменьши tcp_timeout_established до 300 секунд» — могут сломать долгие соединения. Persistent-сессии (websocket, gRPC stream, IPsec, BGP) живут часами без трафика. Если conntrack их выкинет, при следующем пакете они окажутся «вне сессии», и stateful-файрвол их зарежет.

Если очень хочется агрессивных таймаутов — выставляйте отдельно для разных типов трафика через iptables extensions, не глобально.

Отключение conntrack там, где не нужен

Если у вас есть транзитный трафик, который точно не нуждается в NAT и stateful-инспекции — пометьте его как NOTRACK:

iptables -t raw -A PREROUTING -p tcp --dport 443 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 443 -j NOTRACK

Эти пакеты не пойдут в conntrack-таблицу. Подходит для bare-проксей, балансировщиков L4 без NAT и подобного.

Мониторинг

Метрики, которые надо снимать:

# Текущее количество записей
cat /proc/sys/net/netfilter/nf_conntrack_count

# Дропы из-за переполнения
cat /proc/net/stat/nf_conntrack
# выглядит как:
# entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart

В Prometheus это снимается через node_exporter с включённым коллектором conntrack:

node_exporter --collector.conntrack

Алёрт, который я обычно ставлю:

- alert: ConntrackFillingUp
  expr: node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.75
  for: 5m
  labels:
    severity: warning

Реальный кейс

Был у меня инцидент: nginx-балансёр перед бэкендами, держал в среднем 30k соединений. В пике пришёл всплеск, и через 4 минуты — nf_conntrack: table full. Половина запросов отвалилась, half-open соединения зависли в TIME_WAIT.

Что было:

  • nf_conntrack_max = 262144 (дефолт)
  • tcp_timeout_time_wait = 120 (дефолт)
  • TIME_WAIT после пика = 180k за 2 минуты

Что сделал:

  • Поднял nf_conntrack_max до 1 миллиона
  • Снизил tcp_timeout_time_wait до 30 секунд
  • Включил net.ipv4.tcp_tw_reuse=1 на стороне nginx

После этого тот же пик прошёл незаметно. Никаких дропов, conntrack-count в пике — 380k из 1М.

Грабли с модулем

nf_conntrack иногда не подгружается автоматически — например, если в iptables нет stateful-правил. Если параметры sysctl не применяются — проверьте, что модуль вообще загружен:

lsmod | grep conntrack

Если пусто — подгрузите принудительно:

echo 'nf_conntrack' >> /etc/modules-load.d/conntrack.conf
modprobe nf_conntrack

Итог

Conntrack — это та подсистема, про которую вспоминают только когда она ложится. Поставьте nf_conntrack_max адекватно вашей нагрузке (1-2 миллиона на нагруженном балансёре — норма), снизьте tcp_timeout_established до пары часов, мониторьте nf_conntrack_count — и сообщений table full в dmesg вы больше не увидите. А если NAT не используете — подумайте, не отключить ли conntrack совсем.