Ограничение доступа к Nginx только с российских IP-адресов
⚠️ Дисклеймер
Материал предоставлен “как есть” и отражает практический опыт эксплуатации в конкретных инфраструктурах.
Авторы не гарантируют:
- корректность решений в вашей среде;
- совместимость с вашей конфигурацией;
- отсутствие побочных эффектов.
Применение любых инструкций выполняется на ваш страх и риск.
Перед внедрением изменений рекомендуется:
- тестирование на изолированном контуре;
- проверка зависимостей и текущей конфигурации;
- наличие актуальных резервных копий (backup).
GeoIP-фильтрация в Nginx через geo + map
Назначение
Данный подход реализует белый список (whitelist) IP-адресов на уровне Nginx, используя директивы geo и map, вместо большого количества allow/deny.
Это более эффективная и масштабируемая схема для production-систем.
Принцип работы
- Загружается список российских IP-диапазонов (CIDR)
- В директиве
geoформируется переменная$geo_ru, где:1— IP относится к РФ0— всё остальное
- Через
mapформируется логика доступа - В
serverилиlocationпроисходит проверка
Преимущества по сравнению с allow/deny
- O(1) lookup вместо последовательного прохода правил
- меньше нагрузка на worker’ы
- лучше масштабируется на десятки тысяч CIDR
- чище конфигурация
Источник данных
Используется IPdeny:
https://www.ipdeny.com/ipblocks/data/aggregated/ru-aggregated.zone
Генерация geo-конфига
Пример формируемого файла:
geo $geo_ru {
default 0;
5.8.0.0/13 1;
31.13.144.0/20 1;
...
}Скрипт обновления
#!/usr/bin/env bash
set -euo pipefail
URL="https://www.ipdeny.com/ipblocks/data/aggregated/ru-aggregated.zone"
TARGET="/etc/nginx/geo/ru-geo.conf"
TMP=$(mktemp)
curl -fsSL "$URL" -o "$TMP"
echo "geo \$geo_ru {" > "$TARGET"
echo " default 0;" >> "$TARGET"
awk '{ print " " $0 " 1;" }' "$TMP" >> "$TARGET"
echo "}" >> "$TARGET"
rm -f "$TMP"
nginx -t && systemctl reload nginxИспользование через map
map $geo_ru $block_non_ru {
1 0;
0 1;
}Применение
Весь сайт
server {
include /etc/nginx/geo/ru-geo.conf;
if ($block_non_ru) {
return 403;
}
}Только админка
location /admin/ {
if ($block_non_ru) {
return 403;
}
proxy_pass http://127.0.0.1:8080;
}Важно про статику
Если блокировать всё без исключений, сломаются CSS/JS.
Решение:
location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
root /var/www/static;
}И применять блокировку только к backend:
location / {
if ($block_non_ru) {
return 403;
}
proxy_pass http://127.0.0.1:8080;
}Страница отказа
error_page 403 /access-denied/;
location = /access-denied/ {
root /var/www/site;
}Пример HTML:
<!DOCTYPE html>
<html>
<head>
<title>Access denied</title>
</head>
<body>
<h1>Доступ ограничен</h1>
<p>Сервис доступен только из России.</p>
</body>
</html>Cron
17 4 * * * root /usr/local/sbin/update-nginx-ru-geo.shРиски
- GeoIP не идеален
- VPN ломает модель
- требуется мониторинг
Вывод
geo + map — более правильный подход для production по сравнению с allow/deny, так как обеспечивает быстрый lookup, лучше масштабируется и снижает нагрузку на worker-процессы Nginx.
Однако оптимальнее выносить фильтрацию на более ранний уровень:
- iptables / nftables — позволяют отбрасывать нежелательный трафик ещё на уровне ядра, до попадания в Nginx. Это снижает:
- нагрузку на CPU веб-сервера;
- количество соединений, обрабатываемых Nginx;
- влияние атак типа сканирования и brute-force.
- WAF (Web Application Firewall) — добавляет уровень контекстной фильтрации:
- анализирует поведение, а не только IP;
- может блокировать атаки уровня L7 (SQLi, XSS и т.д.);
- даёт централизованное управление политиками безопасности.
Кратко:
geo + map— хороший баланс простоты и производительности на уровне приложенияiptables / nftables— правильное решение для базовой фильтрации на уровне сетиWAF— необходим для защиты от сложных атак
Оптимальная схема — комбинация всех трёх уровней, где каждый решает свою задачу и разгружает следующий.