Найти в Дзене
Пока не взломали

Защита сайта от DDoS на VPS, костыли, которые работают

Наконец-то дождались разговора по существу. Давайте разберем, что реально работает при защите от DDoS на типичных VPS, где канал — это не терабиты из презентаций, а скромные 100 Мбит–1 Гбит или 10 Гбит в лучшем случае. Начнем с неприятной правды: fail2ban + iptables — это защита от мусора, а не от DDoS. Когда у вас 100 Мбит канал и начинается даже небольшой UDP-флуд на 150–200 Мбит, fail2ban вообще ничего не успеет забанить — канал уже забит, пакеты не доходят до приложения, логи не пишутся. Fail2ban работает только постфактум: он парсит логи nginx/apache и банит IP после того, как тот уже достучался до веб-сервера. Если атака идет на network layer (SYN flood, UDP amplification), логи вообще не появятся — пакеты сожрут всю полосу еще до уровня приложения. Теперь про iptables напрямую. Тут начинается математика боли. Iptables обрабатывает правила линейно — каждый пакет проверяется по очереди против всех правил, пока не найдется совпадение. На практике это означает: если у вас 1000 заба
Оглавление

Наконец-то дождались разговора по существу. Давайте разберем, что реально работает при защите от DDoS на типичных VPS, где канал — это не терабиты из презентаций, а скромные 100 Мбит–1 Гбит или 10 Гбит в лучшем случае.

Начнем с неприятной правды: fail2ban + iptables — это защита от мусора, а не от DDoS. Когда у вас 100 Мбит канал и начинается даже небольшой UDP-флуд на 150–200 Мбит, fail2ban вообще ничего не успеет забанить — канал уже забит, пакеты не доходят до приложения, логи не пишутся. Fail2ban работает только постфактум: он парсит логи nginx/apache и банит IP после того, как тот уже достучался до веб-сервера. Если атака идет на network layer (SYN flood, UDP amplification), логи вообще не появятся — пакеты сожрут всю полосу еще до уровня приложения.

Теперь про iptables напрямую. Тут начинается математика боли. Iptables обрабатывает правила линейно — каждый пакет проверяется по очереди против всех правил, пока не найдется совпадение. На практике это означает: если у вас 1000 забаненных IP в iptables, каждый входящий пакет проверяется по всем 1000 правилам. Уже на 10 000 правил производительность начинает сыпаться, а на 100 000 — система просто не держит линию даже на 10 Гбит. Мы с вами знаем, что происходит дальше: CPU упирается в потолок на обработке правил, легитимный трафик тормозит вместе с атакующим, и сайт фактически лежит, хотя технически «защищен».

Решение: ipset + raw table

Если у вас канал 1 Гбит или 10 Гбит и нужно банить тысячи или миллионы IP без деградации производительности — забудьте про длинные списки iptables. Используйте ipset — это hash-таблица для IP-адресов, которая проверяет совпадение за O(1) вместо O(N). Реальные тесты показывают: ipset держит миллионы записей без потери производительности. Да-да, вы не ослышались — можете спокойно пихать туда хоть 10 миллионов IP, lookup time останется константным, в то время как iptables с жалкими 10 000 правил уже проседает на 50%.

Единственное ограничение — память: каждая запись жрёт примерно 64-100 байт, то есть миллион IP ≈ 100 МБ RAM, 10 миллионов ≈ 1 ГБ. На современных серверах это вообще не проблема, зато вы получаете блэклист размером с небольшой ботнет без падения производительности.

Простой пример:

# Установка (Debian/Ubuntu)
apt-get install ipset
# Для CentOS/RHEL
yum install ipset
# Создаем сет на 10 млн IP (с запасом, почему бы нет)
ipset create blacklist hash:ip maxelem 10000000
# Правило в raw table (обрабатывается ДО routing)
iptables -t raw -A PREROUTING -m set --match-set blacklist src -j DROP
# Добавляем IP в блэклист
ipset add blacklist 1.2.3.4
# Смотрим список забаненных IP
ipset list blacklist
# Статистика дропнутых пакетов (смотрим counters в iptables)
iptables -t raw -L PREROUTING -v -n
# Удалить IP из блэклиста
ipset del blacklist 1.2.3.4
# Сохранить ipset (чтобы пережил перезагрузку)
ipset save > /etc/ipset.conf
# И добавить в автозагрузку: ipset restore < /etc/ipset.conf

Почему raw, а не filter? Потому что raw table обрабатывается на самом раннем этапе, до connection tracking и routing. Это дает прирост производительности на 260% по сравнению с обычным DROP в filter/INPUT. Когда атака идет сотнями тысяч пакетов в секунду, каждый сэкономленный CPU-цикл на вес золота.

Что это всё выдержит?

100 Мбит канал: ipset + raw спасут от атак до 150–200 Мбит, если атака не чисто volumetric (то есть не просто “забить канал мусором до отказа”, а смешанная с application-layer запросами). Миллион забаненных IP? Без проблем, performance не просядет. Но если ботнет запустил классический volumetric flood — UDP на 500 Мбит или SYN на гигабит — тут нужен upstream filtering у провайдера, никакой ipset не поможет, когда физическая полоса уже забита на входе.

1 Гбит канал: ipset обязателен. Сотни тысяч забаненных IP — вообще не проблема, можете смело растить блэклист до миллионов, если источников атаки много. Главное чтобы volumetric-составляющая атаки (чистый объём трафика в Мбит/с) не превышала физическую полосу. Если ребята гонят 1.5 Гбит UDP-мусора на ваш 1 Гбит канал — вы всё равно упадёте, это физика.

10 Гбит канал: ipset держит планку. Даже если вам нужно забанить несколько миллионов IP из ботнета — производительность не просядет, только RAM подъест. И молитесь, чтобы атака была не чисто volumetric — на уровне хостинга без upstream scrubbing от ISP терабитные удары не отбить ничем.

Fail2ban — это театр безопасности, iptables без ipset — прямой путь к CPU bottleneck при росте блэклиста, а нормальная защита начинается с ipset в raw table. И да, можете смело грузить туда миллионы записей — hash-таблицы придумали не зря. Когда увидите в “iptables -t raw -L -v” миллионы дропнутых пакетов, поймёте, что оно работает.

Пример с гео-блокировкой. Это реально полезная фича, когда 90% атак идет из определенных стран:

# Установка ipset (если еще не стоит)
apt-get install ipset iprange
# Создаем отдельный set для гео-блокировки (hash:net для подсетей)
ipset create geoblock_cn hash:net maxelem 100000
# Качаем актуальную базу IP Китая от FireHOL
curl -s -o /tmp/country_cn.netset https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/geolite2_country/country_cn.netset
# Загружаем в ipset (пропускаем комментарии и пустые строки)
grep -v '^#' /tmp/country_cn.netset | grep -v '^$' | while read subnet; do
ipset add geoblock_cn $subnet
done
# Или в одну строку через ipset restore (быстрее для больших списков)
echo "create geoblock_cn hash:net maxelem 100000" > /tmp/ipset_load.txt
grep -v '^#' /tmp/country_cn.netset | grep -v '^$' | sed 's/^/add geoblock_cn /' >> /tmp/ipset_load.txt
ipset restore < /tmp/ipset_load.txt
# Добавляем правило в raw table (DROP в самом начале обработки)
iptables -t raw -A PREROUTING -m set --match-set geoblock_cn src -j DROP
# Проверяем сколько подсетей загружено
ipset list geoblock_cn | grep "Number of entries"
# Смотрим статистику дропнутых пакетов
iptables -t raw -L PREROUTING -v -n | grep geoblock_cn

Блокируем DigitalOcean

# Создаем отдельный set для ASN-блокировки
ipset create block_digitalocean hash:net maxelem 2000
# Качаем актуальный список подсетей DigitalOcean
curl -s https://asn.ipinfo.app/api/text/list/AS14061 -o /tmp/as14061.txt
# Загружаем в ipset (каждая строка — это подсеть в CIDR)
while read subnet; do
ipset add block_digitalocean $subnet 2>/dev/null
done < /tmp/as14061.txt
# Или быстрее через одну команду (если файл чистый)
cat /tmp/as14061.txt | xargs -I {} ipset add block_digitalocean {}
# Добавляем правило DROP в raw table
iptables -t raw -A PREROUTING -m set --match-set block_digitalocean src -j DROP
# Проверяем сколько подсетей забанено
ipset list block_digitalocean | grep "Number of entries"
# Смотрим статистику дропнутых пакетов
iptables -t raw -L PREROUTING -v -n | grep block_digitalocean

Можете настроить скрипт, который каждый час обновляет ipset из публичных threat intelligence feeds — блокирует exit-ноды Tor, известные прокси-сети, миллионы IP из спам-баз. FireHOL предоставляет десятки готовых списков: от ботнетов до анонимайзеров.

Если у вашего хостинг-провайдера есть хотя бы базовая защита на уровне L3/L4, то эта связка вполне жизнеспособна даже на скромных каналах.