Безпека

Захист від DDoS: що фільтрує мережа, а що залишається вам

Зміст статті

Стаття для адміністраторів і керівників, які хочуть розуміти, що саме робить захист від DDoS, який входить у вартість кожного нашого сервера. Наприкінці ви знатимете, де його межі, і матимете на сервері власні запобіжники: закриті порти, ліміти запитів у nginx, кеш і fail2ban.

Рівні атак простими словами#

DDoS — це спроба зробити сервіс недоступним, заваливши його трафіком із багатьох адрес водночас. Від рівня, на якому відбувається атака, залежить, хто може її зупинити.

SYN-флуд
Сервер отримує потік запитів на відкриття TCP-з’єднання (пакетів SYN), часто з підроблених адрес. Черга нових з’єднань переповнюється, і справжні клієнти під’єднатися не можуть.
UDP-ампліфікація
Зловмисник надсилає короткі запити чужим відкритим сервісам (DNS, NTP) від імені вашої адреси, а ті відповідають вам значно більшими пакетами й забивають канал.
HTTP-флуд
Тисячі заражених пристроїв відкривають справжні з’єднання і запитують у сайту найважчі сторінки: пошук, кошик, вхід.

Перші дві атаки — мережевого рівня (L3/L4): потік пакетів можна відсіяти в мережі дата-центру ще до сервера. Третя — рівня застосунку (L7): для мережі це звичайний зашифрований HTTPS-трафік, тому зупинити її можуть лише налаштування сервера або зовнішній сервіс, який розбирає HTTP.

Що входить у вартість сервера#

  • Захист від атак мережевого рівня (L3/L4) входить у вартість кожного сервера — в усіх лінійках і в усіх країнах: Польщі, Німеччині, Франції та Великій Британії.
  • Захист спрацьовує автоматично, коли мережа виявляє атаку на адресу сервера.
  • Доплат за обсяг чи тривалість атаки немає.

Два застереження:

  • Фільтрація вмикається після виявлення атаки, тому в перші секунди сміттєвий трафік може дійти до сервера.
  • Фільтр відсіює пакети, а не стежить за вашим застосунком, тож доступності сайту чи облікової системи він не гарантує.

Чого цей захист не охоплює#

  • Атаки рівня застосунку (L7): HTTP-флуд, повільні запити, потік звернень до API.
  • Підбір паролів до SSH, RDP, пошти чи адмінпанелі сайту. Докладніше — у статтях про ключі SSH і безпечний RDP.
  • Вразливості: застаріла CMS чи плагін, невстановлені оновлення.
  • Перевантаження важкими запитами: звіт, що перебирає всю базу, пошуковий робот на сторінках фільтра, власний скрипт у циклі.

Що знадобиться#

  • Доступ до сервера через SSH із правами sudo. Приклади — для Debian 12–13 і Ubuntu 24.04–26.04 з nginx із пакета дистрибутива; для Windows Server придатні лише принципи кроків 1 і 5.
  • Постійна адреса офісу (у прикладах — 203.0.113.10) і другий комп’ютер для перевірки ззовні.

Що зробити самостійно#

1. Закрийте зайві порти#

Подивіться, які сервіси слухають мережу:

sudo ss -tulpn

Локальна адреса 0.0.0.0, * або [::] означає, що сервіс відкритий на всіх адресах. З інтернету мають бути доступні лише порти, потрібні відвідувачам, — зазвичай 80 і 443, а також SSH із входом лише за ключами. Базу даних і RDP назовні не виставляйте: базу прив’яжіть до 127.0.0.1 або до адреси приватної мережі, а до RDP й облікової системи під’єднуйтеся через VPN.

Увага. Помилка в правилах мережевого екрана може відрізати вам доступ через SSH. Спершу дозвольте свій порт SSH і тримайте відкритим другий сеанс. Якщо доступ усе ж зникне, правила доведеться виправляти через віддалену консоль — її надають за запитом через підтримку (на лінійці Лайт її немає). Готові правила — у статті про базовий захист Linux-сервера.

2. Обмежте запити і з’єднання в nginx#

Зони limit_req і limit_conn оголошують у контексті http. Створіть файл /etc/nginx/conf.d/limits.conf:

# адреси, які не обмежуємо (офіс)
geo $limit_exempt {
    default       0;
    203.0.113.10  1;
}
map $limit_exempt $limit_key {
    0 $binary_remote_addr;
    1 "";
}

limit_req_zone  $limit_key zone=perip:10m rate=10r/s;
limit_req_zone  $limit_key zone=login:10m rate=30r/m;
limit_conn_zone $limit_key zone=addr:10m;

limit_req_status  429;
limit_conn_status 429;

Далі ввімкніть обмеження в блоці server вашого сайту (на Debian і Ubuntu це файл у /etc/nginx/sites-available/); замість /login підставте шлях своєї сторінки входу:

server {
    # ... listen, server_name, ssl_certificate ...

    limit_conn addr 40;

    location / {
        limit_req zone=perip burst=40 nodelay;
        # ... proxy_pass або try_files ...
    }

    location = /login {
        limit_req zone=login burst=5 nodelay;
        # ... той самий обробник, що й у location / ...
    }
}

rate — середня частота запитів з однієї адреси, burst — запас на сплеск, а з nodelay запити в межах запасу обслуговуються одразу, без затримки. Усе, що понад запас, отримує код 429. limit_conn обмежує кількість одночасних з’єднань з однієї адреси; для HTTP/2 і HTTP/3 кожен одночасний запит рахується як окреме з’єднання.

Ці числа — лише відправна точка. Щоб дібрати свої, тимчасово додайте в server рядки limit_req_dry_run on; і limit_conn_dry_run on;: nginx нікого не обмежуватиме, але записуватиме в error.log, кого обмежив би. Перевірте конфігурацію і застосуйте її:

sudo nginx -t
sudo systemctl reload nginx

3. Увімкніть кеш для анонімних сторінок#

Найдешевший спосіб пережити HTTP-флуд — не будити застосунок на кожен запит. У nginx для цього є proxy_cache (для PHP-FPM — fastcgi_cache і ті самі директиви з префіксом fastcgi_): навіть кеш на 10 секунд перетворює тисячу однакових запитів на одне звернення до застосунку. Кешуйте лише анонімні сторінки: запити із сесійною cookie чи заголовком Authorization виключіть директивами proxy_cache_bypass і proxy_no_cache, інакше відвідувач може побачити чужий особистий кабінет або кошик.

4. Блокуйте наполегливих за допомогою fail2ban#

Програма fail2ban читає журнали й на певний час блокує в мережевому екрані адреси, які раз у раз порушують правила. Установіть пакети:

sudo apt update
sudo apt install fail2ban nftables

Пакет одразу вмикає jail sshd (так у fail2ban називають набір правил), тому адресу офісу додайте до винятків для всіх jail. Створіть файл /etc/fail2ban/jail.d/nginx-limit-req.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10

[nginx-limit-req]
enabled   = true
backend   = auto
banaction = nftables
logpath   = /var/log/nginx/error.log
findtime  = 10m
maxretry  = 20
bantime   = 1h

backend і banaction задано явно, бо типові значення пакета в різних випусках відрізняються. Якщо сайт має власний error_log, вкажіть у logpath цей файл.

Лише для Debian 12. Jail sshd шукає файл /var/log/auth.log, якого там типово немає, тому служба не запускається і повідомляє про помилку Have not found any log file for sshd jail. Додайте в той самий файл секцію (потрібен пакет python3-systemd, зазвичай його встановлено разом із fail2ban):

[sshd]
backend = systemd

Перевірте конфігурацію і перезапустіть службу:

sudo fail2ban-client -t
sudo systemctl restart fail2ban

Фільтр nginx-limit-req входить до пакета й реагує на відмови limit_req в error.log (записи dry_run не рахуються): 20 відмов за 10 хвилин — блокування на годину для портів 80 і 443. Проти флуду з тисяч адрес fail2ban допомагає мало: це засіб проти окремих настирливих джерел.

5. Для сайтів — зовнішній захист рівня застосунку#

Якщо сайт приносить дохід або вже був мішенню атаки, лімітів на одному сервері проти великого ботнету не вистачить. Потрібен зовнішній сервіс класу WAF або CDN: він приймає HTTP-трафік у своїй мережі, відсіює ботів і передає на сервер лише перевірені запити. Після підключення дозвольте порти 80 і 443 лише з мереж цього сервісу, інакше атака піде на адресу сервера напряму. Налаштуйте також модуль realip у nginx (set_real_ip_from, real_ip_header), інакше для limit_req усі відвідувачі матимуть одну адресу.

Як відрізнити атаку від звичайного перевантаження#

Спершу подивіться на з’єднання:

ss -s
ss -Htn state syn-recv | wc -l
ss -Htn state established '( sport = :80 or sport = :443 )' | awk '{print $NF}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head

Перша команда показує загальну кількість з’єднань, друга рахує напіввідкриті (SYN отримано, підтвердження немає), третя виводить адреси з найбільшою кількістю з’єднань до вебсервера. Далі — журнал вебсервера (формат combined): найактивніші адреси, а якщо замінити $1 на $7 — найчастіші сторінки.

sudo tail -n 50000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -n 20

Якщо є моніторинг, порівняйте на одній часовій шкалі графіки вхідного трафіку, пакетів за секунду, з’єднань, процесора й диска. Без нього поточну швидкість на інтерфейсах покаже sar -n DEV 1 5 з пакета sysstat.

Атака L3/L4
Різкий стрибок вхідного трафіку й пакетів, а запитів у журналі вебсервера не більшає. Багато напіввідкритих з’єднань; сервер може не відповідати навіть на ping.
Атака L7
Шквал запитів у журналі на одну-дві сторінки з безлічі адрес, багато встановлених з’єднань на портах 80 і 443. Процесор зайнятий вебсервером, застосунком і базою, а трафік зростає помірно.
Звичайне перевантаження
Трафік звичний або зростає плавно, запити йдуть від знайомих клієнтів і роботів. Диск чи пам’ять завантажені або все навантаження створює один процес, а початок збігається з подією: звітом, резервним копіюванням, розсилкою.

Що повідомити підтримці#

Якщо це схоже на мережеву атаку або сервер недоступний ззовні, зверніться до технічної підтримки — вона працює цілодобово: телефон +38 044 206 08 08, пошта info@united.net.ua. Одразу вкажіть:

  • час початку атаки (і закінчення, якщо вона вже минула) із часовим поясом;
  • IP-адресу сервера, порт і протокол, на які йде атака, — наприклад, 203.0.113.25, TCP 443;
  • симптоми: сервер зовсім недоступний, втрачаються пакети, сайт відповідає повільно, не працює лише один сервіс;
  • що ви бачили: вивід ss -s, кілька рядків журналу, знімок графіка, трасу mtr ззовні.

Як перевірити результат#

  1. Приберіть рядки dry_run, перезавантажте конфігурацію nginx, як у кроці 2, і з комп’ютера, адреси якого немає у винятках, надішліть серію запитів на сторінку входу. Перші кілька отримають звичайну відповідь, решта — код 429.
    for i in $(seq 1 20); do curl -s -o /dev/null -w '%{http_code}\n' https://example.com/login; done | sort | uniq -c
  2. Відмови видно в error.log, а лічильник Total failed у fail2ban зростає. Після кількох серій адресу буде заблоковано лише для портів 80 і 443; SSH це не зачепить. Остання команда знімає блокування — підставте адресу, з якої перевіряли.
    sudo grep 'limiting requests' /var/log/nginx/error.log | tail -n 5
    sudo fail2ban-client status nginx-limit-req
    sudo fail2ban-client set nginx-limit-req unbanip 198.51.100.7

Типові помилки#

  • Ліміт на адресу без урахування NAT. Увесь офіс або абоненти мобільного оператора виходять в інтернет з однієї адреси. Свої адреси додайте в geo та ignoreip, а значення добирайте в режимі dry_run.
  • Режим dry_run залишився ввімкненим. У цьому режимі nginx лише пише в журнал і нікого не обмежує.

Що далі#