Почему именно 444 и 403: разные задачи, разный эффект
В Nginx есть два популярных способа «отрезать» нежелательный трафик на входе: вернуть клиенту HTTP-ответ (чаще всего 403) или молча разорвать соединение (нестандартный код 444, исторически из nginx).
403 — это корректный HTTP-ответ «доступ запрещён». Он полезен, когда вы хотите явно зафиксировать запрет (например, закрыть админку извне), а также когда вам важно, чтобы отказ был понятен в браузере, мониторинге и при отладке интеграций.
444 — это «ничего не отвечать и закрыть соединение». Для массовых сканеров и примитивных ботов это часто эффективнее: вы не тратите время на генерацию тела ответа и не даёте лишнего «сигнала», что ресурс живой и отвечает. В access log (если вы не меняли формат) это обычно отображается как 444 в поле $status, но на стороне клиента это будет именно обрыв соединения.
Практическое правило:
403— для контролируемых запретов и прозрачной диагностики,444— для «мусорного» трафика (сканеры, агрессивные боты), когда важнее экономить ресурсы и не вступать в диалог.
Сначала диагностика: что именно блокируем (access log analysis)
Перед тем как писать правила map/geo/deny, полезно понять профиль «плохого» трафика. Иначе легко заблокировать полезных роботов (поисковики), аптайм-проверки, API-партнёров или пользователей за корпоративным NAT.
Ниже — несколько команд, которые дают быстрый срез по Nginx access log. Подставьте путь к своему логу (например, /var/log/nginx/access.log или отдельный лог виртуального хоста).
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
Топ IP по числу запросов — первая подсказка: боты часто «светятся» частотой и однотипными URI.
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
Топ URI помогает быстро увидеть типичный паттерн сканеров: /wp-login.php, /xmlrpc.php, /.env, /.git/, /phpmyadmin, /vendor/phpunit и другие «универсальные» пути.
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
Срез по User-Agent часто показывает «bad bots»: пустой UA, мусорные строки, массовые «python-requests», «curl», «Go-http-client». Сами по себе такие UA не всегда зло, но в связке с подозрительными URI и частотой — хороший сигнал.
Важно: банить нужно реальный IP (set_real_ip_from) за прокси/CDN
Если Nginx стоит за балансировщиком, reverse proxy или CDN, в логах и в $remote_addr вы можете видеть IP прокси, а не клиента. Тогда любые geo/deny по IP будут бессмысленны: вы либо не заблокируете атакующего, либо случайно «отрежете» весь трафик через прокси.
Решение — настроить Real IP модуль: принимать «истинный» адрес из заголовка (чаще X-Forwarded-For или X-Real-IP) только от доверенных источников.
http {
set_real_ip_from 192.0.2.10;
set_real_ip_from 198.51.100.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
}
Где:
set_real_ip_from— список доверенных прокси/балансеров (IP или подсети), которым вы разрешаете «сообщать» реальный адрес.real_ip_header— заголовок, из которого берём адрес клиента.real_ip_recursive— корректная обработка цепочкиX-Forwarded-For, если прокси несколько.
После этого $remote_addr станет «похож» на реальный IP клиента, а блокировки по IP начнут работать ожидаемо. Обязательно перепроверьте формат логов и убедитесь, что вы больше не анализируете IP прокси.

Быстрый блок по IP: deny + 403 (простой и надёжный)
Самый прямолинейный способ остановить конкретные IP/подсети — директива deny. Она возвращает 403. Это удобно, когда у вас есть подтверждённый список источников (например, IP, которые долбят админку, или подсеть, которая сканирует уязвимости).
server {
listen 80;
server_name example.com;
location / {
deny 203.0.113.55;
deny 203.0.113.0/24;
allow all;
try_files $uri $uri/ =404;
}
}
Плюсы: очевидно, быстро, читаемо. Минусы: deny не умеет условия по UA/URI и при большом списке хуже поддерживается (хотя десятки записей обычно не проблема).
«Тихий» бан: return 444 и где его ставить
Чтобы включить nginx 444, используют return 444;. Обычно его располагают как можно раньше — на уровне server (или раннего location), чтобы не тратить ресурсы на лишние проверки.
Пример «закрыть всё, кроме allowlist» как шаблон (в реальности так делают редко, но для понимания полезно):
server {
listen 80;
server_name example.com;
if ($remote_addr != 198.51.100.25) { return 444; }
location / {
try_files $uri $uri/ =404;
}
}
Директива if в Nginx — инструмент, с которым нужно быть аккуратным. На практике для условных блокировок надёжнее и масштабируемее использовать map и geo: они вычисляют переменные «без сюрпризов» и хорошо читаются.
map: блокируем по User-Agent и другим признакам
map — один из самых удобных инструментов для политики фильтрации: вы описываете условия, а результат складываете в переменную (например, $block_ua). Затем в server делаете одно короткое решение: return 444 или return 403.
Пример: переменная будет равна 1 для подозрительных User-Agent.
http {
map $http_user_agent $block_ua {
default 0;
"" 1;
~*"python-requests" 1;
~*"masscan" 1;
~*"zgrab" 1;
~*"sqlmap" 1;
}
server {
listen 80;
server_name example.com;
if ($block_ua) { return 444; }
location / {
try_files $uri $uri/ =404;
}
}
}
Здесь if используется в безопасной форме if (var) { return ... }. Обычно это не вызывает проблем, потому что не участвует в переписывании URI и не строит сложную ветвящуюся логику.
Практика: не блокируйте «curl» или «Go-http-client» автоматически. Эти UA часто используют легитимные проверки, интеграции, CI и некоторые SDK. Лучше блокировать по UA только в связке с подозрительными URI или другими сигналами.
map по URI: режем типовые пути сканеров
Большая часть block scanners решается точечным закрытием явно вредных маршрутов. Например, если у вас не WordPress, запросы к /wp-login.php и /xmlrpc.php почти всегда шум.
http {
map $request_uri $block_uri {
default 0;
~*"^/wp-login\.php" 1;
~*"^/xmlrpc\.php" 1;
~*"^/\.env$" 1;
~*"^/\.git/" 1;
~*"^/phpmyadmin" 1;
}
server {
listen 80;
server_name example.com;
if ($block_uri) { return 444; }
location / {
try_files $uri $uri/ =404;
}
}
}
Выбор между return 444 и return 403 тут зависит от политики. Для «мусорных» URI чаще ставят 444. Для осознанно закрытых зон (например, /admin с внешнего мира) логичнее 403, чтобы при отладке было видно, что это именно запрет.
Если вы часто работаете с кэшем и диапазонными запросами, полезно учитывать, что часть «подозрительных» паттернов приходит от клиентов и плееров. В таких случаях помогает разнести правила по зонам и аккуратно смотреть на метрики. См. также: Range-запросы и кэш в Nginx/Apache: практические нюансы.
geo: блокируем по IP и подсетям аккуратно и быстро
geo создаёт переменную на основании IP-адреса клиента. Это отличный вариант для ручного бана подсетей, allowlist офисных IP, ограничения доступа к админке, «мягкого» контроля партнёрских интеграций.
http {
geo $block_ip {
default 0;
203.0.113.55 1;
203.0.113.0/24 1;
}
server {
listen 80;
server_name example.com;
if ($block_ip) { return 403; }
location / {
try_files $uri $uri/ =404;
}
}
}
Почему иногда лучше 403 вместо 444 при блоке по IP? Потому что IP-блоки часто «операционные»: вы будете разбираться, не задели ли вы пользователя или партнёра. Понятный HTTP-ответ ускоряет диагностику.
Комбинируем map + geo: один флаг блокировки и единая точка return
Когда правил становится много, главная цель — не превратить конфиг в набор разрозненных «заплаток». Удобный приём: собрать общий флаг $block из нескольких источников, а затем сделать ровно одно решение на входе.
http {
map $http_user_agent $block_ua {
default 0;
"" 1;
~*"zgrab" 1;
~*"sqlmap" 1;
}
map $request_uri $block_uri {
default 0;
~*"^/\.env$" 1;
~*"^/\.git/" 1;
}
geo $block_ip {
default 0;
203.0.113.0/24 1;
}
map "$block_ua$block_uri$block_ip" $block {
default 0;
~*"1" 1;
}
server {
listen 80;
server_name example.com;
if ($block) { return 444; }
location / {
try_files $uri $uri/ =404;
}
}
}
Идея простая: map и geo вычисляют «0/1», а итоговое решение централизовано. Такой подход легче сопровождать, переносить между проектами и откатывать.
403 для админки, 444 для мусора: типовой практичный сценарий
Частая задача: админка доступна только с корпоративных IP, всё остальное — 403 (с понятной диагностикой), а мусорные сканы по типовым путям — 444 на входе.
http {
geo $admin_allow {
default 0;
198.51.100.10 1;
198.51.100.0/24 1;
}
server {
listen 80;
server_name example.com;
location ^~ /admin/ {
if ($admin_allow = 0) { return 403; }
proxy_pass http://backend;
}
location / {
try_files $uri $uri/ =404;
}
}
}
Тут 403 оправдан: это «намеренно закрытая» зона, и по ответу проще отличить запрет от проблем проксирования.

Логи и наблюдаемость: не потерять сигнал в шуме
Когда вы вводите return 444, часть инструментов аналитики будет интерпретировать это как сетевую ошибку. Это нормально, но важно не перепутать такую «ошибку для бота» с реальными проблемами доступности.
Что обычно помогает на практике:
- Помечать отказы отдельной переменной и выводить её в access log, чтобы быстро отделять «наши блокировки» от проблем приложения.
- Следить за долей 444/403 во времени: резкий рост часто означает новую волну сканирования.
- После настройки
set_real_ip_fromпроверить, что в логах действительно клиентские IP, иначе статистика по источникам будет «плоской» (один IP прокси).
Проверка и безопасное внедрение
Любые правила блокировки лучше выкатывать итеративно:
- Сначала добавить правила в «наблюдательном» режиме: не блокировать, а только помечать переменную и писать её в лог.
- Проверить 24–72 часа, что не попали легитимные клиенты и боты.
- Включить блок (
return 444илиreturn 403) и продолжать мониторить.
После изменений всегда прогоняйте синтаксическую проверку и перечитывание конфига:
nginx -t
systemctl reload nginx
Типичные ошибки и как их избежать
Блокировка по IP без realip
Самая частая причина «почему deny/geo не работает»: вы баните IP прокси. Настройте set_real_ip_from и только потом добавляйте IP-правила.
Слишком агрессивный блок по User-Agent
Под «bad bots» легко случайно записать всё подряд. Делайте исключения для своих интеграций и мониторинга, а блокировку по UA используйте как один из сигналов, а не единственный.
Смешивание логики по всему конфигу
Когда return/deny раскиданы по десяткам location, сопровождение становится дорогим. Централизуйте решение через map/geo и один флаг $block.
Короткий итог
nginx 403 подходит для управляемых запретов и понятной диагностики. nginx 444 — эффективный способ «не разговаривать» со сканерами и снизить лишнюю нагрузку. Для аккуратной политики блокировок лучше всего работает связка map (условия по UA/URI/заголовкам) + geo (условия по IP) + единая точка решения через return 444 или return 403. И не забывайте про set_real_ip_from: без него любые IP-блокировки за прокси и CDN легко превращаются в фикцию.
Если вам нужно быстро поднять изолированную конфигурацию Nginx с гибкими правилами фильтрации и логированием, обычно удобнее делать это на VDS, чтобы иметь полный контроль над сетевыми настройками и конфигами.


