Аудит блокировок IP в Nginx: как найти deny, geo, limit_req и limit_conn и понять причину 403 | AdminWiki

Аудит блокировок IP в Nginx: как найти deny, geo, limit_req и limit_conn и понять причину 403

24 сентября 2026 9 мин. чтения

Зачем аудитить блокировки IP в Nginx и с чего начать

Аудит блокировок IP в Nginx отвечает на один вопрос: какие правила могут отдать клиенту 403 и не попал ли под них тот, кто вам нужен. Правила разбросаны по nginx.conf, каталогам conf.d/ и sites-enabled/, а иногда лежат в отдельных файлах с зонами. Одни вы задали руками и они работают постоянно, другие включаются только при превышении порога под нагрузкой. Начинайте с вывода итоговой конфигурации, а не с чтения основного файла: именно include-файлы чаще всего скрывают лишний deny all.

Практическая цель аудита: собрать полный список запретов, сопоставить его с реальными клиентами и убрать те, что бьют по своим. Проверять правки до перезагрузки обязательно, иначе конфиг с несуществующей зоной или опечаткой в адресе оставит сервер без рабочего правила.

Чем статические блокировки отличаются от динамических

Разница определяет всю методику поиска. Статические правила лежат в конфиге текстом и действуют всегда, динамические создаются зонами в памяти и срабатывают только по порогу. Диагностику 403 начинайте со статических: их проще найти и они дают предсказуемый код ответа.

ТипДирективыКонтекстКогда срабатываетКод ответа
Статический запретdeny, allowhttp, server, location, limit_exceptПока правило есть в конфиге403
Статическая карта адресовgeohttpПо значению переменной для адреса клиентаЛюбой из вашего правила, обычно 403 через return
Ограничение скоростиlimit_req_zone, limit_reqЗона в http, правило в http, server, locationПри превышении rate и burst503 по умолчанию, а limit_req_status меняет его на 429 или другой
Ограничение соединенийlimit_conn_zone, limit_connЗона в http, правило в http, server, locationПри превышении числа одновременных соединений503, а limit_conn_status меняет код

Минимальные примеры выглядят так. Статическая блокировка: deny 192.0.2.1; в блоке server или location. Динамическое ограничение: сначала зона limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; в блоке http, затем правило limit_req zone=one burst=5 nodelay; в location.

Опасная настройка, которую находим при аудите чаще всего: limit_req_status 403. С ней динамическое ограничение начинает выглядеть как статическая блокировка, и вы будете искать deny там, где его нет. Проверьте эту директиву отдельно.

Как вывести полный конфиг через nginx -T

Команда nginx -T выводит полную действующую конфигурацию в stdout, включая все обработанные include-директивы, и попутно проверяет синтаксис. Команда nginx -t только проверяет синтаксис конфигурационного файла и пытается открыть указанные в нём файлы, но ничего не печатает, поэтому для аудита одного теста недостаточно. Это поведение описано в разборе проверки конфигурации Nginx и в man-странице nginx(8).

Сохраните вывод в файл, чтобы искать по нему и не гонять Nginx повторно:
nginx -T > /tmp/nginx-full.conf

Дальше работайте с файлом: он содержит склейку nginx.conf, conf.d/*.conf, sites-enabled/* и всех остальных include. Маска include /etc/nginx/conf.d/*.conf; разворачивается полностью, поэтому правило из забытого файла вы увидите в общем дампе.

В контейнере команда выполняется через exec:
docker exec nginx nginx -T
docker compose exec nginx nginx -T

В Kubernetes, если Nginx развёрнут как Deployment:
kubectl exec -it deploy/nginx -- nginx -T > /tmp/nginx-full.conf

Перед любыми правками сделайте копию каталога конфигов:
cp -a /etc/nginx /etc/nginx.bak.20260923

Как найти все статические блокировки: deny и geo

Ищите сразу по дампу, а не по каталогам, так вы гарантированно получите порядок, в котором Nginx видит правила. Проверяйте не только deny, но и allow: в директивах доступа порядок задаёт результат, и один allow перед deny all полностью меняет логику блока.

Базовые команды по полному конфигу:
grep -n 'deny\|allow' /tmp/nginx-full.conf
grep -n 'geo ' /tmp/nginx-full.conf

Поиск deny и allow в include-файлах

Иногда дампа нет под рукой, тогда ищите по файловой системе:
grep -rn 'deny\|allow' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/

Директивы доступа работают в http, server, location и limit_except. Nginx проверяет правила последовательно в порядке их появления, и результат решает первое совпадение. Типовой шаблон защиты админки:
location /admin {
allow 10.0.0.0/8;
deny all;
}

Здесь первая строка пропускает внутреннюю сеть, вторая закрывает всех остальных. Перестановка этих двух строк заблокирует вообще всех, включая администраторов, и это самая частая ошибка в правилах доступа. Больше примеров ограничений по IPv4 и IPv6 с проверкой через curl собрано в материале про права доступа к данным в Nginx и контроль доступа.

Блокировки через geo выглядят иначе: директива объявляет карту адресов, а решение принимает переменная. Карта описывается только в контексте http:
geo $blocked {
default 0;
192.0.2.0/24 1;
}
Внутри server или location правило превращается в ответ: if ($blocked) { return 403; }

Как найти конкретный IP в правилах блокировки

Проверка конкретного адреса сводится к grep по дампу:
nginx -T | grep -n '192.0.2.1'
nginx -T | grep -n '192.0.2.0/24'

Для карт geo ищите по имени переменной, а не по адресу, потому что подсети в них могут быть заданы диапазонами и списками. Если адрес не найден ни в deny, ни в geo, проверьте, не приходит ли 403 из другого места: от return, от прав на файл или от бэкенда.

Как найти динамические ограничения: limit_req и limit_conn

Динамические ограничения ищутся в два прохода: сначала зоны, потом правила, которые их используют. Правило без объявленной зоны не работает, и Nginx может не запуститься с ошибкой unknown limit_req zone, а вот зона без правила просто висит в памяти без эффекта.

Поиск по дампу:
nginx -T | grep -n 'limit_req\|limit_conn'

Где искать зоны limit_req_zone и limit_conn_zone

Зоны объявляются в контексте http и часто вынесены в отдельный файл вроде /etc/nginx/conf.d/limits.conf. Примеры:
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=5r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

Ключ зоны определяет, по чему считается лимит. С $binary_remote_addr ограничение считается на адрес клиента, с $server_name на виртуальный хост, с $http_x_api_key на ключ. По данным блога NGINX, состояние примерно 16 000 IP-адресов занимает 1 МБ, то есть зона на 10 МБ рассчитана примерно на 160 000 адресов. Это ориентир из практического материала, а не норма из официальной документации модуля ngx_http_limit_req_module, поэтому при оценке запаса памяти отталкивайтесь от фактического числа уникальных клиентов.

Правила размещаются в server или location:
limit_req zone=req_limit burst=10 nodelay;
limit_conn addr 10;

Параметр burst задаёт очередь запросов сверх лимита, nodelay отдаёт их сразу без задержки. Без nodelay лишние запросы обслуживаются с паузами, и это выглядит для клиента как медленный сервер, а не как блокировка.

Как отличить rate limit от статической блокировки

Различие видно по коду ответа и по сообщению в error.log. Для ограничения скорости подтверждено поведение по умолчанию: NGINX отвечает кодом 503 клиенту, превысившему лимит, и пишет в лог строку вида limiting requests, excess: 1.000 by zone "mylimit" с указанием зоны и величины превышения — это зафиксировано в материале NGINX о rate limiting. Для deny ожидаемый код 403 и запись access forbidden by rule, для limit_conn — запись limiting connections; эти два случая в доступных источниках не подтверждены, поэтому проверяйте их по фактическому логу вашей установки, а не считайте гарантированными.

Смотрите журнал ошибок в реальном времени:
tail -f /var/log/nginx/error.log | grep -i 'limiting'

Если код 403 встречается вместе с настройкой limit_req_status 403, динамический лимит маскируется под статический запрет. Тогда ищите строки limiting в error.log: они есть, даже когда клиент видит 403. Обратный случай тоже возможен: deny может быть перекрыт error_page, и клиент увидит нестандартную страницу.

Как понять причину 403: пошаговая диагностика

403 в Nginx появляется минимум по пяти причинам: deny, geo или if с return, ограничение с limit_req_status 403, нехватка прав на файл, ответ апстрима, а также внешний WAF или фильтр. Порядок разбора всегда одинаков: сначала логи, потом правила, потом права и бэкенд.

Что искать в error.log и access.log

В error.log ключевая строка про блокировку содержит текст access forbidden by rule. Пример записи:
2026/09/23 10:00:00 [error] 1234#0: *1 access forbidden by rule, client: 192.0.2.1, server: example.com, request: "GET /admin HTTP/1.1"

Если вместо этого вы видите open() вроде failed (13: Permission denied), причина в правах на файл или каталог, а не в блокировке IP. Проверьте каталог раздачи:
ls -la /var/www/html

Срез по коду 403 удобно смотреть в access.log:
grep ' 403 ' /var/log/nginx/access.log | tail -20
Сопоставьте адрес и время с моментом, когда клиент сообщил о проблеме. Готовые связки grep и awk для поиска ошибок и подозрительной активности разобраны в статье про анализ логов Nginx и Apache.

Если в access.log запроса нет вообще, он не дошёл до Nginx: смотрите балансировщик, firewall или CDN. Полезно поднять детализацию логирования на время проверки:
error_log /var/log/nginx/error.log debug;

Как проверить, что IP блокируется на уровне Nginx, а не upstream

Сравнить источник ответа помогает переменная $upstream_status в формате лога. Логика такая: если 403 отдаёт бэкенд, $upstream_status тоже показывает 403; если запрос не ушёл на апстрим, значение $upstream_status остаётся пустым или равным дефису. Это рабочая эвристика, а не гарантия из документации: в доступных источниках поведение $upstream_status при deny не подтверждено, поэтому сверяйте его с записью access forbidden by rule в error.log.

Фрагмент log_format для такой проверки:
log_format diag '$remote_addr $status $upstream_status "$request"';
Дальше выборка по нужному адресу:
grep '192.0.2.1' /var/log/nginx/access.log | tail -20

Признак блокировки на стороне Nginx: в error.log есть access forbidden by rule, а $upstream_status пустой. Признак проблемы на бэкенде: $upstream_status равен 403, а строки access forbidden by rule в логах нет. Общий алгоритм разбора кодов 403, 404 и 500 с проверкой прав, PHP-FPM и виртуальных хостов собран в статье про диагностику ошибок веб-сервера.

Чек-лист аудита блокировок IP в Nginx

Прогоняйте этот список целиком, чтобы не пропустить правила из include-файлов и динамические лимиты.

  1. Сделайте копию конфигов: cp -a /etc/nginx /etc/nginx.bak.20260923
  2. Выведите полный конфиг: nginx -T > /tmp/nginx-full.conf
  3. Найдите статические запреты: grep -n 'deny\|allow\|geo' /tmp/nginx-full.conf
  4. Найдите динамические лимиты: grep -n 'limit_req\|limit_conn' /tmp/nginx-full.conf
  5. Проверьте коды подмены: grep -n 'limit_req_status\|limit_conn_status' /tmp/nginx-full.conf
  6. Проверьте 403 в access.log: grep ' 403 ' /var/log/nginx/access.log | tail -20
  7. Проверьте error.log на access forbidden by rule и на Permission denied
  8. Протестируйте конкретный адрес через curl и сопоставьте время с логами
  9. Убедитесь, что конфиг валиден: nginx -t
  10. Перезагрузите без разрыва соединений: nginx -s reload

Отдельно ведите список адресов, которые блокируете намеренно: мониторинг, офисная сеть, VPN администраторов. Через месяц никто не вспомнит, откуда взялся deny в sites-enabled, и дежурный потратит час на поиск. Для постоянного контроля доступа и следа изменений настройте журналирование и правила автоматической блокировки, как описано в гайде по мониторингу и аудиту безопасности Nginx.

Как проверить результат аудита на практике

Убедиться, что адрес действительно блокируется, быстрее всего через curl. Запрос без заголовков проверяет реальный путь клиента:
curl -I https://example.com
curl -I https://example.com | head -1

Подстановка адреса через X-Forwarded-For сработает только тогда, когда в Nginx настроены real_ip_header и set_real_ip_from. Модуль ngx_http_realip_module меняет адрес и необязательный порт клиента на переданные в указанном поле заголовка: set_real_ip_from задаёт доверенные адреса, которые передают верный адрес, а real_ip_header задаёт поле заголовка, значение которого используется для замены адреса клиента — это описано в документации модуля ngx_http_realip_module. Без этой настройки ограничения считаются по адресу, с которого пришёл TCP-запрос, то есть по $remote_addr. Проверка с чужого IP через заголовок без модуля real_ip даст неверный результат:
curl -I -H 'X-Forwarded-For: 192.0.2.1' https://example.com

Если блокировка нужна только в одном location, тестируйте именно этот путь: корень сайта может отдавать 200, а закрытая админка 403. Сопоставьте ответ со строкой в access.log, время запроса должно совпадать с точностью до секунды.

Не блокируйте себя во время правок: перед reload держите открытой вторую SSH-сессию, добавьте свой адрес в allow, проверьте конфиг через nginx -t и только потом выполняйте nginx -s reload. Правило, которое валидно синтаксически, но закрывает весь трафик, откатывается из созданной ранее копии конфигов.

Поделиться:
Сохранить гайд? В закладки браузера