Содержание

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

Одна из самых полезных функций в Nginx это rate limit. Она позволяет ограничить количество HTTP запросов от пользователей в определённый промежуток времени. Лимиты можно применять к простым GET запросам домашней страницы сайта или же к POST запросам формы логина.

При помощи Rate limit можно замедлить перебор паролей для злоумышленника, или предотвратить DDoS-атаки, снизив количество входящих запросов до типичных для пользователей значений. И тем временем определять по логам атакуемые URL. Также можно предотвратить перегрузку вышестоящих серверов (upstream) во время внезапного наплыва пользователей на сайт.

Как лимиты вообще работают?

Ограничения запросов в Nginx использует алгоритм "дырявого ведра", широко используемый, чтобы справится со всплесками трафика, когда ширина канала ограничена. Дырявое ведро — хорошая аналогия, ведь, из него вода медленно вытекает сквозь дырки, и оно может переполнится, если добавлять воду слишком быстро. В терминах обработки запросов, вода — это входящий трафик, а ведро — очередь запросов на обработку FIFO-алгоритмом. Протекающая сквозь ведро вода — это запросы, переданные на обработку, а всё, что проливается через край — отклонённые запросы пользователей.

Простая настройка Rate Limit

Ограничения настраиваются при помощи двух директив, limit_req_zone и limit_req находящиеся во встроенном модуле ngx_http_limit_req_module.

Рассмотрим простой пример:

limit_req_zone $binary_remote_addr zone=darkfire:10m rate=10r/s;server { location /darkfire/ { limit_req zone=darkfire; proxy_pass https://fb.me/hostingconsultant; }}

В этом примере директива limit_req_zone описывает лимиты, которые далее используются для ограничения числа пользовательских запросов в локации /darkfire/ при помощи limit_req. Директива limit_req_zone описывается в http секции конфигурации и может использоваться во множестве контекстов. Параметры этой директивы:

Если места для добавления новой записи недостаточно, Nginx удаляет самую старую запись, чтобы предотвратить исчерпание памяти. Если процесс Nginx’а не может создать новую запись, из зоны может быть удалено до двух записей, которые не использовались в предыдущие 60 секунд. Если свободного пространства все равно не хватает, возвращается ошибка 503 (Service Temporarily Unavailable)

Директива limit_req_zone задаёт параметры лимитов и общей памяти, но не управляет применением самих лимитов к запросам. Для окончательной настройки необходимо добавить в блоки location или server директиву limit_req. Выше мы применили лимиты для локации /darkfire/ в блоке server нашей конфигурации. Тем самым мы ограничили число запросов с уникального IP-адреса клиента одним, сделанным не ранее чем 100 миллисекунд после предыдущего.

Обработка очередей и всплесков

Что же будет, если в нашей конфигурации пользователь отправит 2 запроса к нашему серверу за 100 мс? Сервер вернёт код 503 для второго запроса. Это может оказаться не очень удачным решением, ведь в реальности наши приложения способны обрабатывать такие всплески. Необходимо настроить буфер для входящих запросов. За эту настройку отвечает параметр burst в директиве limit_req. Посмотрим на примере: location /darkfire/ { limit_req zone=darkfire burst=20; proxy_pass https://fb.me/hostingconsultant;} В этом примере, для локации darkfire, каждый запрос, который приходит чаще, чем установленное ограничение, будет помещён в очередь, размер которой 20 запросов.

Параметр burst настраивает количество запросов, которые пользователь может сделать прежде, чем лимиты будут применены и Nginx начнёт отбрасывать запросы.

Это означает, что если от одного IP адреса приходит одновременно 21 запрос, то Nginx отправить в обработку первый, а остальные 20 будут помещены в очередь. Затем каждые 100 миллисекунд запросы из очереди будут отправлены в обработку и пользователь увидит ошибку 503 только если количество запросов от него не уместится в этой очереди или они будут прибывать слишком быстро (быстрее чем 1 запрос в 100 миллисекунд если вы этого ещё не запомнили).

Очереди без задержек

Для большинства инсталляций разработчики Nginx рекомендуют использовать burst и nodelay совместно.

В конфигурациях с простым использованием burst есть недостаток. Они обеспечивают сервер плавным потоком трафика, но слишком медлительны для пользователя. Так в примере выше, обработка 20 пакетов от пользователя занимает 2 секунды. Если вам не подходит такое поведение, добавьте параметр nodelay вместе с burst:

location /cooladmin/ { limit_req zone=cooladmin burst=20 nodelay; proxy_pass https://fb.me/hostingconsultant;}

С параметром nodelay Nginx по-прежнему выделяет очередь для каждого потока запросов, но не делает паузы в отправках запросов из очереди. Вместо этого запросы пересылаются на обработку как только возможно, а очистка слотов в очереди происходит как и выше (да-да, те самые 1 раз в 100 миллисекунд).

Теперь предположим, что к нам поступает 21 запрос с одного IP-адреса одновременно. Nginx незамедлительно отправляет все эти запросы в обработку, и начинает освобождать очередь, очищая 1 слот каждые 100 миллисекунд. Если мы получили 25 запросов от одного IP адреса, то четыре из них отклоняются со статусом 503, а остальные отправляются в обработку.

Теперь предположим, что через 101 миллисекунду после первой порции запросов отправляется еще 20 запросов одновременно. Свободен только 1 слот в очереди, поэтому Nginx передаёт в обработку только один запрос, а остальные 19 отклоняет с кодом 503. Если спустя 501 миллисекунду пришло ещё 20 новых запросов, то в очереди появилось 5 свободных слотов, потому 5 запросов будут обработаны, а остальные 15 отклонены.

Примеры расширенных конфигураций

Читайте также: Интеграция Fail2ban и Nginx nginx-limit-req

Комбинируя базовые ограничения скорости с другими функциями Nginx можно реализовать более тонкие ограничения трафика.

Белые списки

Например, мы можем наложить ограничения на количество обращений к серверу со всех IP-адресов, кроме внесённых в белый список.

geo $darkfire { default 1; 10.0.0.0/8 0; 192.168.0.0/24 0;}map $darkfire $darkfire_key { 0 ""; 1 $binary_remote_addr;}limit_req_zone $darkfire_key zone=cool_admin_zone:10m rate=5r/s;server { location / { limit_req zone=cool_admin_zone burst=10 nodelay; # ... }}

В этом примере мы используем директивы geo и map. Блок geo устанавливает значение 0 адресам из белого списка и 1 - всем остальным (по умолчанию). Затем мы используем map для назначения лимитов:

Таким образом, мы обрабатываем одновременно адреса из белого списка и адреса клиентов. Для адресов из белого списка переменная $darkfire_key будет содержать пустую строку, и директива limit_req_zone для них применена не будет. Как следствие, на адреса из сетей 10.0.0.0/8 и 192.168.0.0/24 не будет наложено ограничений. Для адресов, не входящих в белый список, будет применён лимит в 5 запросов в секунду (или 1 запрос в 200 миллисекунд).

Такой лимит мы применили к корневой "/" локации сервера. Ещё мы настроили возможность всплеска до 10 дополнительных запросов, которые будут обработаны так быстро, как это позволит наш сервер, без дополнительных задержек, так как используется nodelay.

Использование нескольких ограничений в одной локации

Вы можете использовать несколько директив limit_req в одной локации вашего сервера. В таком случае применяться будет только наиболее строгий лимит. Например, если несколько ограничений вводят задержку на входящие запросы, будет использована самая долгая. Если несколько директив разрешают запрос, а одна директива его отклоняет - запрос будет отклонён. Расширим предыдущий пример, введём дополнительные лимиты для адресов из белого списка:

http { # ... limit_req_zone $darkfire_key zone=darkfire_zone:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=darkfire_zone_wl:10m rate=15r/s; server { # ... location / { limit_req zone=darkfire_zone burst=10 nodelay; limit_req zone=darkfire_zone_wl burst=20 nodelay; # ... } }}

Адреса из белого списка не попадают под первый лимит darkfire_zone, но попадают под второй darkfire_zone_wl и к ним будет применяться ограничение в 15 запросов в секунду (или 1 запрос в 66 миллисекунд). Для всех остальных адресов, по прежнему, применяется лимит в пять запросов в секунду.

В обоих случаях мы дополнительно настроили поддержку всплеска и отключили задержку на перенаправление пакетов в обработку.

Настройка связанных функций

Логирование

По умолчанию лог Nginx будет содержать отложенные или отброшенные лимитами записи в формате:

1998/04/04 04:04:04 [error] 228#0: *228 limiting requests, excess: 1.000 by zone "darkfire", client: 192.251.68.254, server: darkfire, request: "GET / HTTP/1.0", host: "dieg.info"

Поля этого лога:

По умолчанию лог отклонённых запросов будет располагаться на уровне error, и будет показан в топике [error]. Лог задержанных запросов будет находится на другом уровне, в info по умолчанию. Чтобы поменять такое поведение, используйте директиву limit_req_log_level. В примере ниже мы помещаем лог отклонённых запросов на уровень warn:

location /darkfire/ { limit_req zone=coollimit burst=20 nodelay; limit_req_log_level warn; proxy_pass https://fb.me/hostingconsultant;}

Коды ошибок, возвращаемые клиенту

По умолчанию Nginx возвращает код 503 (Service Temporarily Unavailable ), если клиент превысил допустимые лимиты. Используйте директиву limit_req_status, чтобы изменить код на другой. Например, на HTTP код 429:

location /darkfire/ { limit_req zone=coollimit burst=20 nodelay; limit_req_status 429;}