Атаки на отказ в обслуживании (Denial‑of‑service attack, DoS), к сожалению, стали обыденным явлением для публичных веб‑сервисов. Типов и разновидностей атак на отказ в обслуживании существует огромное количество, поэтому мы не будем даже пытаться провести классификацию. Скажем только, что для полноценной защиты от распределённых DoS‑атак (DDoS — Distributed DoS) необходима специализированна�� облачная система. В этой статье мы проведём обзор возможностей защиты от обычных DoS‑атак и простейших DDoS‑атак встроенными средствами веб‑сервера Angie.

Навигация по циклу

  1. Почему стоит переходить на Angie.

  2. Установка Angie из пакетов и в докере.

  3. Переезд с Nginx на Angie. Пошаговая инструкция.

  4. Настройка location в Angie. Разделение динамических и статических запросов.

  5. Перенаправления в Angie: return, rewrite и примеры их применения.

  6. Сжатие текста в Angie: статика, динамика, производительность.

  7. Серверное кэширование в Angie: тонкости настройки.

  8. Настройка TLS в Angie: безопасность и скорость.

  9. Настройка Angie в роли обратного HTTP-прокси.

  10. Балансировка нагрузки для HTTP(S) в Angie.

  11. Мониторинг Angie с помощью Console Light и API.

  12. Балансировка и проксирование L4-трафика в Angie.

  13. Клиентское кэширование в Angie.

  14. Динамические группы проксируемых серверов в Angie.

  15. Мониторинг Angie с Prometheus и Grafana.

  16. Отказоустойчивый кластер Angie с VRRP и Keepalived.

  17. Контроль доступа в Angie.

  18. Аутентификация клиентов в Angie с помощью TLS-сертификатов.

  19. Кастомизация Angie (njs, Lua, Perl).

  20. Запуск CGI-скриптов в Angie.

  21. Защита от DoS-атак в Angie стандартными модулями.

  22. Защита от DoS-атак в Angie (дополнительные средства).

Видеоверсия

Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.

Ограничение частоты запросов

Рассмотрим самый наивный и простой сценарий атаки: запуск утилиты нагрузочного тестирования (ab, wrk и так далее), нацеленной на динамический документ. При отсутствии какой‑либо защиты большинство приложений через какое‑то время перестанут отвечать от перегрузки тяжёлыми запросами. Для организации такой атаки даже не потребуются специальные ресурсы: достаточно одного компьютера и среднего интернет‑канала.

Первая мера по защите от такого типа DoS‑атак — ограничение частоты запросов к бэкенду. Как правило, бэкенд потребляет большую часть ресурсов системы, имеет жесткие ограничения по пропускной способности (запросов в секунду, RPS) и поэтому является целью атаки.

Ограничением частоты запросов в Angie занимается стандартный модуль Limit Req. Само ограничение реализовано через алгоритм Leaky Bucket. Для ограничения запросов нужно создать зону разделяемой памяти (в контексте http) и применить ограничение с помощью директивы limit_req в локации или сервере. Например, так:

http {
limit_req_zone $binary_remote_addr zone=lone:10m rate=5r/s;
	...
	server {
	…
	location / {
		proxy_pass http://localhost:8080;
		limit_req zone=lone burst=5;
	}
	…
}

В настройках зоны (limit_req_zone) мы указываем ключ, для которого учитываются запросы, её размер (10 MB) и ограничение частоты — 5 запросов в секунду. Здесь возникает сразу несколько вопросов: как определить размер зоны в памяти, ключ и частоту запросов?

Размер области памяти можно определить из расчёта 8 тысяч состояний на один мегабайт зоны. Ключ определяется задачами: в нашем случае мы отслеживаем запросы по IP‑адресу клиента. Если зона будет использоваться для нескольких сайтов, можно добавить в ключ значение домена (например, переменную $host). Проблема использования IP клиента заключается в невозможности разделить реальных пользователей сайта из‑за общего при доступе в Интернет через NAT (что для IPv4 стандартная ситуация). Для решения этой задача можно добавлять в ключ значение сессионных cookie или какой‑нибудь другой идентификатор.

Сложнее дело обстоит с выбором частоты запросов. Как минимум, хорошо знать предельные значения пропускной способности бэкенда и поставить их в лимит. Далее можно применить логику и здравый смысл при выставлении лимитов на различные локации. Например, для отдачи целиковых документов в большинстве случаев будет достаточно 3–5 запросов в секунду. При этом, для высокочастотных локаций (например AJAX‑запросы) эти значения нужно увеличивать в разы (конечно, создать для этого отдельную зону).

Теперь давайте подробнее разберём саму директиву ограничения запросов:

limit_req zone=lone burst=5;

В ней мы указываем зону ограничений (одна зона может участвовать в нескольких директивах limit_req). Кроме того, мы разрешаем всплески до 5 запросов сверх лимита, указанного в зоне. Например, к нам прилетает в одну миллисекунду 5 запросов от клиента — это значительно превышает скорость 5 RPS (один запрос в 200 мс), но мы разрешаем их обработать. Приходить на бэкенд они будут с лимитированной частотой (1 запрос в 200 мс). Если мы хотим пропускать всплески без задержки (как есть), то добавляем атрибут nodelay:

limit_req zone=lone burst=5 nodelay;

Также есть возможность указать количество запросов, после которого они будут задер��иваться (параметр delay):

limit_req zone=lone burst=5 delay=2;

Перед включением в боевой режим полезно протестировать значения лимитов и узнать, насколько часто они будут срабатывать в реальных условиях. Эту задачу решает директива limit_req_dry_run:

location / {
	proxy_pass http://localhost:8080;
	limit_req zone=lone burst=5;
	limit_req_dry_run on;
}

В такой конфигурации лимиты будут учитываться, но не применяться (запросы не задерживаются и не отбрасываются). При этом в error.log сервера будут записаны сообщения о превышении лимитов. Запись о задержке запроса: 

2026/02/12 16:07:09 [warn] 4775#4775: *43 delaying request, excess: 9.970, by zone "lone", client: 192.168.122.1, server: _, request: "GET / HTTP/1.0", host: "192.168.122.141"

Запись об ограничении запросов:

2026/02/12 16:08:14 [error] 4775#4775: *220 limiting requests, excess: 10.750 by zone "lone", client: 192.168.122.1, server: _, request: "GET / HTTP/1.0", host: "192.168.122.141"

Уровни сообщений о блокировке и задержке запросов определяются директивой limit_req_log_level.

При отклонении запросов по умолчанию используется код ответа 503, но его можно изменить директивой limit_req_status.

Отслеживать работу ограничений запросов можно в штатном инструменте мониторинга Angie Console Light (закладка HTTP-зоны):

График ограничения запросов
График ограничения запросов

Важно заметить, что модуль Limit Req блокирует запросы, но не имеет функции постоянной блокировки при злостном нарушении лимитов. Для реализации таких блокировок нам потребуется внешний инструмент, например пакет Fail2Ban (он будет разобран в другой статье).

Лимитирование активных подключений

Помимо лимита на частоту запросов, в Angie можно активировать ограничения на количество клиентских подключений (стандартный модуль Limit Conn). Однако, в отличие от лимитов на частоту запросов, ограничение подключений имеет очень узкое применение.

Дело в том, что работает это ограничение на количество активных подключений. То есть подключений, обрабатывающих запросы в данный момент. Также важно, что для протоколов HTTP/2 и HTTP/3 каждый активный запрос (stream) приравнивается к подключению. Также не забываем про работу клиентов через NAT при использовании IP‑адреса в качестве ключа. Кстати, если ограничение запросов можно использовать только в рамках модуля HTTP (так как там существуют запросы), то ограничение активных подключений можно использовать и в модуле Stream (TCP, UDP). 

Чтобы настроить такие ограничения как и в случае с запросами создаём зону разделяемой памяти для хранения состояний (в контексте http или stream). 

limit_conn_zone $binary_remote_addr zone=addr:10m;

Логика такая же, как и с запросами: ключ зоны (бинарный IP), название и размер. Чтобы применить ограничение, добавляем директиву limit_conn в блок server или location:

server {
	…
limit_conn           addr 1;
limit_conn_log_level error;

	location / {
		proxy_pass http://localhost:8080;
	}
}

По умолчанию при ограничении формируется ответ с кодом 503, изменить это можно директивой limit_conn_status. Уровень записи сообщения об ограничении в error_log выставляется директивой limit_conn_log_level. Сообщения об ограничении подключений имеют следующий вид:

2026/02/13 14:13:08 [error] 3622#3622: *85 limiting connections by zone "perip", client: 192.168.122.1, server: _, request: "GET / HTTP/1.0", host: "192.168.122.141"

Отслеживать работу ограничений подключений можно в штатном инструменте мониторинга Angie Console Light (закладка HTTP‑зоны):

График ограничения подключений
График ограничения подключений

Ограничение полосы пропускания

В предыдущих частях статьи мы говорили преимущественно о защите от излишней нагрузки на бэкенд, но теоретически есть еще одна возможности исчерпания ресурсов сервера. А именно, полная утилизация исходящего канала (или интерфейса). При этом со стороны атакующего достаточно иметь полосу пропускания, превышающую возможности сервера, что легко достигается при нескольких атакующих хостах. Иногда такая атака может произойти непреднамеренно, например, при размещении тяжелого по размеру статического контента на сервере. По умолчанию клиенты будут скачивать ресурс с максимальной доступной ему скоростью, что может привести к загрузке канала сервера. Ско��ее всего, это не приведёт к полной недоступности сервера за счет механизмов разделения потоков в TCP, но может повлиять на объем потребления трафика и скорость загрузки небольших ресурсов. 

В таком случае бывает полезно ограничить полосу для отдельных клиентов. Для этого можно использовать стандартную директиву limit_rate. Например, в конфигурации location (ограничивать можно на уровне http, server, location и if в location):

server {
	…
	location /video {
		limit_rate 5000k;
	}
}

Ограничение измеряется в байтах в секунду и действует на один активный запрос. То есть, если клиент откроет несколько соединений, то лимиты будут суммироваться. В параметре директивы limit_rate можно использоват переменные, что позволяет реализовать более сложную логику ограничения с помощью map. Например, ограничивать только выбранных клиентов по заголовку User-Agent.

http {
  map $http_user_agent $rate {
		default 0;
		~*(bot|crawl|python|apachebench|curl) 500k;
  }
  server {
	…
	location /video {
		limit_rate $rate;
	}
  }
}

Здесь мы ограничиваем скорость только для некоторых типов клиентов, выбранных по регулярному выражению с помощью заголовка User‑Agent. Возможно, вы вам потребуется вообще заблокировать доступ для некоторых типов клиентов, об этом пойдёт речь в следующей части статьи. 

Блокировка на основе User-Agent

Допустим, мы наблюдаете больше количество запросов от ботов, которые определяются по User-Agent. Конечно, можно закрыть доступ с помощью /robots.txt, но это будет работать только для легитимных ботов, следующих правилам:

User-Agent: Testbot
Disallow: /

Для остальных ботов можно настроить блокировку по совпадению User‑Agent с регулярными выражениями. Пример такой конфигурации:

http {
  map $http_user_agent $limit_bots {
	default 0;
	~*(libwww|Python|perl|urllib|Curl|PycURL|Pyth|webcraw) 1;
  }

server {
	…
	if ($limit_bots = 1) {
   		return 444;
    }
  }
}

В примере выше мы определили переменную $limit_bots на основе map и заголовка User-Agent. Далее на уровне сервере создали блок if для блокировки ботов. В данном примере используется директива return со специальным кодом 444, который для Angie означает вместо ответа клиенту просто разорвать соединение. 

При использовании этого метода блокировок будьте осторожны с выбором регулярных выражений, чтобы не запретить легитимных ботов (например, поисковых).

Итоги

Мы собрали в этой статье стандартные инструменты для борьбы с простыми DoS‑атаками. Вполне вероятно, что вы сможете отразить атаку с их помощью. В следующей части мы разберём сторонние модули для борьбы с DoS‑атаками, которые позволяют значительно расширить возможности противодействия.

Следующая статья цикла: Защита от DoS-атак в Angie (дополнительные средства).