Техническая глубина
Укрепление Linux-сервера против DDoS: все параметры sysctl, conntrack и nftables
Обновлено: август 2026 г. · Каждый параметр sysctl и nftables, его значение и проверка · Время чтения ~22 мин

На Linux-сервере укрепление против DDoS защищает три конечных ресурса: очереди SYN и accept, таблицу conntrack и файловые дескрипторы. У каждого параметра есть значение по умолчанию, функция и счётчик для проверки, и укрепление начинается со знания всех трёх. Значения выбираются по вашей базовой линии; скопированное значение либо бесполезно, либо режет ваших же пользователей.
Это руководство отвечает на один вопрос: как укрепить сам Linux-сервер, без всякого устройства перед ним, против той части DDoS-атаки, что исчерпывает состояние. Промежуточные слои защиты мы здесь не обсуждаем. Речь идёт о собственных настройках ядра сервера.
Большинство текстов на эту тему — это список sysctl: двадцать строк без объяснений. Список как будто работает, потому что значения безобидны. В день атаки команда, не знающая, что делает каждая строка, не знает и на какой счётчик смотреть. Ниже каждый параметр идёт вместе со своим значением, своей функцией и своей командой проверки. То, что атака может исчерпать на хосте, конечно и счётно. Каждому ресурсу отведён свой раздел.
| Устройство | Ресурс | Симптом | Команда проверки |
|---|---|---|---|
| Очередь SYN / accept | Новые соединения молча отваливаются по таймауту | nstat -az | grep -i listen | |
| Таблица conntrack | dmesg: 'nf_conntrack: table full, dropping packet' | conntrack -C; conntrack -S | |
| Файловые дескрипторы | accept() возвращает EMFILE; порт слушает, но никого не принимает | cat /proc/<pid>/limits; cat /proc/net/sockstat | |
| TIME_WAIT / осиротевшие сокеты | Исчерпаны эфемерные порты или память; 'Out of socket memory' в логе | ss -tan state time-wait | wc -l; cat /proc/net/sockstat | |
| Буферы сокетов | Легитимный трафик теряется под нагрузкой | netstat -s | grep -i prune; cat /proc/net/softnet_stat |
Все пять строк — предмет этой статьи, и все пять измеряются на хосте. Насыщение процессора по числу пакетов (softirq) — отдельный слой, он разобран в руководстве по сетевому стеку.
0. Сначала базовая линия: нельзя настроить то, что не измерил
Перед укреплением запишите цифры обычной недели. Каждый порог ниже выбирается относительно них.
# Snapshot of sockets (established, syn-recv, time-wait breakdown)
ss -s
# Socket counters: tcp inuse / orphan / tw, tcp mem
cat /proc/net/sockstat
# conntrack occupancy and ceiling
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# Cumulative TCP events — handshake, queue drops
nstat -az | grep -Ei 'syn|listen|drop|overflow'
# To watch over time
watch -n1 'cat /proc/net/sockstat; echo; nstat | grep -Ei "listen|syncookie"'
Вывод этих пяти команд — ваша базовая линия. Неделю спустя вы запускаете те же команды и видите, что изменилось. Беда скопированного порога в том, что он этот шаг пропускает.
1. Сторона SYN: очередей две, а не одна
Ядро держит две очереди на каждый слушающий сокет. Полуоткрытая очередь держит соединения с
незавершённым рукопожатием (SYN_RECV); её размер — tcp_max_syn_backlog. Очередь accept
держит завершённые рукопожатия в ожидании accept(); её потолок — меньшее из somaxconn и
backlog вызова listen() в приложении.
Из-за этого разделения поднятие одного лишь sysctl часто ничего не меняет: если приложение слушает с backlog 128, то что вы записали в ядро — неважно.
# /etc/sysctl.d/90-ddos.conf — SYN layer
net.ipv4.tcp_syncookies = 1 # cookie при переполнении очереди; современное значение по умолчанию
net.ipv4.tcp_max_syn_backlog = 8192 # размер полуоткрытой очереди
net.core.somaxconn = 8192 # потолок очереди accept
net.ipv4.tcp_synack_retries = 2 # сокращает жизнь неотвеченного SYN_RECV
net.ipv4.tcp_syn_retries = 3 # для исходящих соединений; сторона клиента
Поднимите backlog приложения одновременно, иначе один somaxconn ничего не даст:
# nginx: listen backlog выравнивается по somaxconn
listen 443 ssl backlog=8192;
Проверка. Читаем сокеты в SYN_RECV и сбросы очереди:
# How many connections are half-open right now?
ss -tan state syn-recv | wc -l
# Accept queue occupancy: Recv-Q is the current backlog, Send-Q the ceiling
ss -ltn
# Queue overflow counters — if these leave zero, the accept chain is narrow
nstat -az | grep -E 'TcpExtListenDrops|TcpExtListenOverflows'
# Did SYN cookies actually engage?
nstat -az | grep -i syncookie
Если SyncookiesSent больше нуля, ваша очередь переполнялась хотя бы раз; это сигнал поднять
backlog или задействовать вышестоящий слой. Пока SYN cookies активны, а метки времени TCP
выключены, для тех соединений теряются масштабирование окна и SACK. Поэтому не полагайтесь на
cookie, а стремитесь к очереди, которая не переполняется вовсе.
tcp_abort_on_overflow при переполнении очереди accept шлёт RST вместо молчаливого сброса.
Значение по умолчанию (0) — молчаливый сброс, и обычно оно верное: клиенту остаётся место для
повторной попытки. Если за сервером стоит балансировщик со своей логикой повторов, значение 1 —
более честный сигнал:
sysctl -w net.ipv4.tcp_abort_on_overflow=1 # only with a reason
2. conntrack: как только таблица полна, новый поток не пройдёт
Отслеживание соединений netfilter держит запись на каждый поток. Когда таблица заполняется, ядро сбрасывает пакет нового потока. Симптом коварен: существующие сессии живут, все новые остаются снаружи, а график говорит «трафик в норме».
# Current occupancy and ceiling
conntrack -C
cat /proc/sys/net/netfilter/nf_conntrack_max
# How many flows in which state? (a SYN_SENT flood shows up here)
conntrack -L 2>/dev/null | awk '{print $4}' | sort | uniq -c | sort -rn
# Have drops started?
dmesg | grep -i 'nf_conntrack: table full'
conntrack -S | grep -Eo 'drop=[0-9]+'
Рычагов три. Размер, время жизни и вовсе не отслеживать.
# /etc/sysctl.d/90-ddos.conf — conntrack
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 3600 # default 432000 (5 days)
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 30 # clear half-open fast
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_loose = 0 # do not track one-way flows
Размер хеш-таблицы (buckets) задаётся параметром модуля, а не sysctl; держа его хотя бы на
уровне одной восьмой потолка, вы снижаете число коллизий:
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize
# persistent: in /etc/modprobe.d/nf_conntrack.conf
# options nf_conntrack hashsize=262144
Третий рычаг держит бесстатусный высокообъёмный трафик полностью вне таблицы. Raw-цепочка nftables:
table inet raw {
chain prerouting {
type filter hook prerouting priority raw; policy accept;
udp dport 53 notrack
tcp dport 53 notrack
}
}
Бесстатусная служба вроде авторитетного DNS этим правилом становится невосприимчива к conntrack-флуду.
Цена очевидна: правила ct state к этому трафику не применяются.
Порог тревоги: заведите в мониторинг занятость на уровне семидесяти процентов потолка.
awk -v max="$(cat /proc/sys/net/netfilter/nf_conntrack_max)" \
-v cur="$(cat /proc/sys/net/netfilter/nf_conntrack_count)" \
'BEGIN{ printf "conntrack %d/%d = %.0f%%\n", cur, max, 100*cur/max }'
3. Файловые дескрипторы: побеждает самое короткое звено цепи
Каждое открытое соединение — это файловый дескриптор. Когда они кончаются, accept() возвращает
EMFILE; служба поднята, порт слушает, но не принимает никого. Атаки медленными соединениями метят
именно сюда.
Ограничение — это цепь, и правит самое нижнее звено:
# System-wide ceiling
cat /proc/sys/fs/file-max
# Per-process absolute ceiling
cat /proc/sys/fs/nr_open
# How many descriptors are open now / what is the ceiling?
cat /proc/sys/fs/file-nr
# /etc/sysctl.d/90-ddos.conf — system wide
fs.file-max = 2097152
fs.nr_open = 1048576
Настоящее узкое место почти всегда — лимит юнита службы, потому что на большинстве
дистрибутивов он остаётся низким. Долговременное решение — drop-in systemd; не воюйте с ulimit,
который описывает вашу оболочку, а не службу:
# /etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=262144
systemctl daemon-reload
systemctl restart nginx
# The service's REAL limit (not the shell's):
systemctl show nginx -p LimitNOFILE
cat /proc/$(pgrep -o nginx)/limits | grep 'open files'
# Overall socket usage:
cat /proc/net/sockstat # sockets: used ...; TCP: inuse ...
4. TIME_WAIT и осиротевшие сокеты: тихое исчерпание
Флуд короткоживущими соединениями исчерпывает два ресурса через накопление TIME_WAIT и
осиротевшие сокеты: диапазон эфемерных портов и память TCP-сокетов. Симптом — строка
TCP: out of memory или Out of socket memory в dmesg.
# State distribution
ss -tan state time-wait | wc -l
ss -tan state fin-wait-1 | wc -l
# Orphan / tw counts and tcp mem pressure
cat /proc/net/sockstat
# tcp_mem: low pressure high (third number is the hard ceiling in pages)
cat /proc/sys/net/ipv4/tcp_mem
# /etc/sysctl.d/90-ddos.conf — TIME_WAIT / orphan
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_tw_reuse = 1 # safe for outbound connections
net.ipv4.ip_local_port_range = 1024 65535
tcp_tw_reuse безопасен только для исходящих соединений и при включённых метках времени; не
используйте старый tcp_tw_recycle, который ломает клиентов за NAT и из современных ядер всё равно
удалён. При превышении tcp_max_orphans ядро закрывает лишнее через RST и пишет
too many orphaned sockets в dmesg; это защитное поведение, поэтому держите лимит выше вашей
легитимной нагрузки.
5. Буферы сокетов и очередь приёма
Этот слой не останавливает атаку; он снижает подавление легитимного трафика во время неё. Если очередь на пути приёма переполняется, хороший пакет теряется вместе с плохим.
# /etc/sysctl.d/90-ddos.conf — buffers and receive queue
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 16384 # NIC → kernel queue
net.core.netdev_budget = 600 # packets processed per softirq
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
Проверка — терял ли пакеты путь приёма, исчерпан ли бюджет softirq:
# softnet_stat: col 1 processed, col 2 DROPPED, col 3 left by budget/time-limit
cat /proc/net/softnet_stat
# TCP-side prune / collapse (a sign of buffer pressure)
netstat -s | grep -Ei 'prune|collapse|out of'
nstat -az | grep -Ei 'TcpExtTCPRcvQDrop|PruneCalled'
Если второй столбец softnet_stat растёт, поднимите netdev_max_backlog; если растёт третий —
поднимите netdev_budget. Хронический рост третьего столбца — заодно и ранний признак того, что
число пакетов начинает насыщать процессор. Эта черта — граница данной статьи, и она переходит к
руководству по тюнингу сетевого стека.
6. Подделка источника, ICMP и поверхность маршрутизации
Небольшая, но необходимая группа. Против поддельных адресов источника и поверхности отражения:
# /etc/sysctl.d/90-ddos.conf — anti-spoof and ICMP
net.ipv4.conf.all.rp_filter = 1 # reverse-path filtering (strict)
net.ipv4.conf.default.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1 # close the smurf surface
net.ipv4.icmp_ratelimit = 1000
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_timestamps = 1 # required for tw_reuse and PAWS
rp_filter=1 (строгий режим) верен только на сервере с одним аплинком. На машине с
асимметричной маршрутизацией, принимающей трафик более чем одним путём, строгий режим сбрасывает
легитимные пакеты; там выбирают 2 (нестрогий режим). Неверный режим может вызвать простой вообще
без атаки; не ставьте 1, не зная, какой режим вам нужен:
# Effective rp_filter per interface (the max of 'all' and the interface applies)
for i in /proc/sys/net/ipv4/conf/*/rp_filter; do echo "$i = $(cat $i)"; done
# How many packets did rp_filter drop?
nstat -az | grep -i 'IPReversePathFilter'
7. Ограничение скорости по источнику в nftables
Увеличение очередей — пассивная половина. Активная ограничивает долю одного источника, внутри ядра. Динамическое множество nftables делает это на каждый адрес источника:
table inet filter {
set flood4 { type ipv4_addr; flags dynamic; timeout 60s; }
set flood6 { type ipv6_addr; flags dynamic; timeout 60s; }
chain input {
type filter hook input priority filter; policy accept;
ct state established,related accept
ct state invalid drop # drop half/broken flows
# Per-source new-connection rate — IPv4 and IPv6 separately
tcp dport { 80, 443 } ct state new \
add @flood4 { ip saddr limit rate over 30/second } drop
tcp dport { 80, 443 } ct state new \
add @flood6 { ip6 saddr limit rate over 30/second } drop
}
}
Проверка — какие источники упёрлись в лимит, сколько пакетов сбросило правило:
# Sources that hit the limit and entered the set
nft list set inet filter flood4
# Rule counters (if you add 'counter' to the rule)
nft list ruleset | grep -A2 flood
# How much did ct state invalid drop — a counted rule example
nft add rule inet filter input ct state invalid counter drop
Если пропустить множество для IPv6, половина защиты отсутствует. Порог берётся из вашего
измерения: для легитимной толпы за NAT 30 может быть слишком мало, для корпоративного API — много.
Первую неделю гоняйте правило с counter вместо drop, смотрите, кто попадается, и переходите на
drop, лишь убедившись, что легитимный трафик не режется.
Есть ещё synproxy — рукопожатие берёт на себя ядро, а до службы за ним доходят только
завершённые соединения — но его взаимодействие с асимметричной маршрутизацией и настройками
conntrack тонкое; не выносите его в продакшен без проверки в лаборатории.
Честный предел ограничения по источнику: оно работает против флуда с немногих адресов. Флуд, размазанный по тысячам адресов, где каждый держится ниже порога на адрес, способен заполнить ваш канал в сумме, и это правило его не видит. Форму той же логики в масштабе префикса мы разобрали в руководстве по ковровой бомбардировке.
8. Поведение соединений на уровне приложения
Соединение, прошедшее очередь ядра, доходит до приложения, где ожидающее соединение тоже держит ресурс. Эта статья о ядре, но два sysctl влияют на поведение приложения напрямую:
net.ipv4.tcp_keepalive_time = 300 # detect a dead connection early
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
Атаки медленными соединениями (открыть соединение и цедить данные по капле) на уровне ядра полностью не решаются; настоящая защита — собственные таймауты веб-сервера. Это мы разбираем по серверам: руководства по укреплению nginx и укреплению Apache дают эти таймауты директива за директивой.
Полный файл и загрузка за один раз
Разделы вместе:
sudo tee /etc/sysctl.d/90-ddos.conf >/dev/null <<'EOF'
# --- SYN ---
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
net.ipv4.tcp_synack_retries = 2
# --- conntrack ---
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 30
# --- file descriptors ---
fs.file-max = 2097152
fs.nr_open = 1048576
# --- TIME_WAIT / orphan ---
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_tw_reuse = 1
# --- buffers ---
net.core.netdev_max_backlog = 16384
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# --- anti-spoof / ICMP ---
net.ipv4.conf.all.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_timestamps = 1
EOF
sudo sysctl --system
# verify it applied
sudo sysctl -a 2>/dev/null | grep -E 'somaxconn|syncookies|conntrack_max|file-max|rp_filter'
Ни одно из значений не требует перезагрузки, и все обратимы. Но все они — отправные точки, подтягиваемые вверх или вниз по вашей собственной базовой линии. Руководство, дающее вам точные числа, вводит в заблуждение, потому что верное число зависит от числа ваших сессий, от распределения источников и от вашего железа.
Счётчики для вывода в мониторинг
Укрепление не чувствуют, его читают. Заведите в систему тревог эти шесть сигналов:
# 1. SYN queue drops
nstat -az | grep -E 'ListenDrops|ListenOverflows'
# 2. SYN cookie use (an overflow indicator)
nstat -az | grep -i syncookiessent
# 3. conntrack occupancy ratio (>70% warn)
cat /proc/sys/net/netfilter/nf_conntrack_count
# 4. file descriptor use
cat /proc/sys/fs/file-nr
# 5. receive-path drops
awk '{s+=$2} END{print "softnet drops:", s}' /proc/net/softnet_stat
# 6. socket memory pressure
grep -E 'TCP:' /proc/net/sockstat
Эти шесть строк меньше чем за минуту во время атаки отвечают на вопрос «сужается хост или заполняется канал».
Честный предел этого слоя
Всё до этого места сводится к одной фразе: сервер защитим против атак, исчерпывающих его собственное состояние. За её пределами — два случая, и ни один не решается на хосте.
Первый — насыщение канала. Объём выше вашего канала доступа никогда не доходит до ядра; sysctl не может сбросить пакет, которого не видит. Выше этой черты предмет уже не сервер, а сетевая архитектура.
Второй — скорость пакетов. Ещё до заполнения канала число пакетов в секунду способно насытить
способность ядра обрабатывать прерывания; симптом — третий столбец softnet_stat и уход процессора
в softirq. Настройки того слоя — распределение прерываний, очереди драйвера, RSS/RPS, путь приёма —
это отдельный мир, разобранный команда за командой в
руководстве по тюнингу сетевого стека.
Выше этих двух порогов укрепление хоста не становится бесполезным; напротив, поднимая пол, именно оно держит сервер на ногах, пока не включится верхний слой, и после того как включится. Но само по себе оно недостаточно, и относиться к нему так, будто достаточно, — значит порождать ложную уверенность.
Порядок применения
- Запишите базовую линию (раздел 0). Это стоит недели терпения; это самый дорогой шаг.
- Напишите
90-ddos.conf, загрузите черезsysctl --system, проверьте черезgrep. - Выровняйте backlog приложения (
listen ... backlog=) сsomaxconn. - Добавьте drop-in
LimitNOFILEв юниты служб, проверьте черезsystemctl show. - Настройте размер/время жизни conntrack; выведите бесстатусные службы из таблицы через
notrack. - Погоняйте ограничение nftables в режиме
counterнеделю, затем переключите наdrop. - Заведите шесть счётчиков в мониторинг как тревоги.
Ни один из семи шагов не требует перезагрузки, и все обратимы.
Частые вопросы
- Собрать все настройки в один файл?
- Да, в отдельный файл под /etc/sysctl.d/. Вместо правки собственных файлов дистрибутива создайте файл с высоким номером, например 90-ddos.conf; высокий номер означает, что на конфликтующем параметре побеждает ваш. Загрузка: sysctl --system перечитывает весь каталог, sysctl -p /etc/sysctl.d/90-ddos.conf — только этот файл. Файл нужен для сохранения между перезагрузками; значение, записанное через sysctl -w, теряется при перезагрузке.
- Требуют ли эти изменения перезагрузки?
- Почти ни одно; параметры sysctl вступают в силу сразу. Исключения — несколько значений, читаемых очень рано, и размер хеш-таблицы conntrack: nf_conntrack_buckets меняется в рантайме через /sys/module/nf_conntrack/parameters/hashsize, а не через sysctl. Drop-in systemd для файловых дескрипторов требует systemctl daemon-reload и перезапуска службы; вживую, как sysctl, он не применяется.
- Можно ли полностью отключить conntrack?
- Выборочно, на сервере без NAT и без нужды в отслеживании состояния — да. Цель notrack в raw-цепочке nftables держит определённый трафик полностью вне таблицы; порт 53 авторитетного DNS-сервера — классический случай. Тогда этот трафик не может заполнить таблицу conntrack, потому что таблица для него не ведётся. Цена в том, что правила ct state к этому трафику не применяются; вы выбираете это сознательно.
- Останавливают ли эти настройки объёмную атаку?
- Нет. Трафик, заполняющий ваш канал доступа, никогда не доходит до ядра, а ядро не может управлять пакетом, которого не видит. Каждая настройка здесь защищает от исчерпания состояния: очереди, таблицы, дескрипторы и буферы. Объём выше ёмкости вашего канала — не проблема хоста и на хосте не решается; выше этой черты остаётся только сетевая сторона.
- Задавать эти значения внутри контейнера или на хосте?
- Большинство параметров сетевого стека привязаны к сетевому пространству имён и должны задаваться в собственном пространстве контейнера; значение хоста внутрь не переходит. В Kubernetes безопасное подмножество разрешено через поле sysctl, остальное требует привилегированного контекста. Общесистемные значения вроде fs.file-max остаются на хосте и охватывают все контейнеры. Не считайте параметр привязанным к пространству имён без проверки для критичной настройки; вывод sysctl -a внутри пространства имён может отличаться от хоста.
- Как проверить всё это без атаки?
- В лаборатории, на своём же трафике. Наращивайте скорость SYN с помощью hping3 и скорость новых соединений с помощью ab или wrk, и на каждом шаге читайте nstat -az, conntrack -C и ss -s. Ищете вы то, какой счётчик сдвигается при какой скорости; эта скорость и есть ваш реальный порог, а не число из даташита. В продакшене отклонение тех же счётчиков от базовой линии — первый сигнал для вывода в мониторинг.
Опубликовано: август 2026 г.
Руководство обновляется по мере выхода новых моделей и условий лицензирования. Как мы сравниваем производителей