Ограничение доступа по IP — одна из тех вещей, которые я настраиваю на каждом втором проекте, и при этом каждый раз нахожу что-то новое. Казалось бы, простая задача: пустить одних, закрыть других — а нюансов столько, что можно написать книгу.
Зачем вообще ограничивать доступ по IP
Честно говоря, когда клиенты впервые слышат про IP-фильтрацию, многие думают, что это что-то из арсенала параноиков. На деле — это один из самых практичных инструментов безопасности. И вот почему.
У меня был клиент — небольшой интернет-магазин на Битриксе. Его админку атаковали брутфорсом примерно раз в неделю. Пробовали разные пароли, разных пользователей, иногда 500–700 запросов в час. Установили fail2ban — помогло частично, но нагрузка на сервер всё равно была. Решение оказалось простым: закрыли /bitrix/admin/ по IP, оставив доступ только с офисного адреса и домашнего адреса разработчика. Атаки не прекратились, но теперь они просто получают 403 ещё на уровне nginx — без какой-либо нагрузки на PHP и MySQL.
Второй типичный кейс — закрытые тестовые и staging-окружения. Зачем гуглу и яндексу индексировать ваш dev.mysite.ru? Правильно, незачем. Закрываем по IP — и всё, проблема решена. Без паролей, без noindex-метатегов, которые кто-нибудь забудет убрать перед релизом.
Третий случай, с которым я сталкиваюсь регулярно: корпоративные сервисы, API-эндпоинты, личные кабинеты с чувствительными данными. Если ваш API вызывается только с конкретных серверов — логично закрыть его для всего остального мира. Это не замена авторизации, но хороший дополнительный слой. Кстати, про защиту API я подробно писал в статье защита API-ключей на сайте — там есть много смежных вещей.
Ограничение по IP в Nginx: основные подходы
Nginx — мой основной инструмент для IP-фильтрации. Работаю с ним на серверах под Ubuntu 22.04 и 24.04, на CentOS 7/8 (хотя CentOS постепенно ухожу от него в пользу Rocky Linux). Конфигурация простая, но есть пара тонкостей, которые важно знать.
Базовый вариант — разрешить только конкретные IP и всем остальным вернуть 403:
location /bitrix/admin/ {
allow 192.168.1.100; # офисный IP
allow 78.45.123.67; # IP разработчика
allow 10.0.0.0/8; # внутренняя сеть
deny all;
# остальные директивы локации
try_files $uri $uri/ /bitrix/urlrewrite.php;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Здесь важный момент: порядок директив allow/deny имеет значение. Nginx обрабатывает их сверху вниз и останавливается на первом совпадении. Поэтому deny all всегда пишем последним. Это звучит очевидно, но я видел конфиги, где deny all стоял первым, и люди не понимали, почему ничего не работает.
Второй подход — использование geo-модуля. Он полезен, когда нужно управлять доступом к разным частям сайта из одного места, и когда список IP длинный:
# В блоке http{} в nginx.conf или в отдельном include-файле
geo $allowed_ip {
default 0;
192.168.1.0/24 1; # офисная подсеть
78.45.123.67 1; # разработчик
185.220.0.0/16 1; # IP партнёра
}
server {
listen 443 ssl;
server_name mysite.ru;
location /admin/ {
if ($allowed_ip = 0) {
return 403;
}
# ... остальные директивы
}
location /api/internal/ {
if ($allowed_ip = 0) {
return 403;
}
# ... остальные директивы
}
}
Но честно говоря, использование if в nginx — это скользкая дорожка. В официальной документации nginx есть даже статья "If is Evil". Для простых случаев с IP лучше всё-таки использовать стандартные allow/deny. Geo + if оставляю для сложных сценариев, где нужно комбинировать условия.
Ограничение доступа через .htaccess (Apache)
Apache встречается реже в новых проектах — я сам перешёл на nginx ещё лет 8 назад. Но на shared-хостингах он до сих пор доминирует, и там .htaccess — единственный инструмент. Поэтому разберём и его.
Синтаксис зависит от версии Apache. В Apache 2.2 и Apache 2.4 он разный, и это постоянный источник путаницы:
# Apache 2.4 (актуальный синтаксис)
<Directory "/var/www/html/admin">
Require ip 192.168.1.100
Require ip 78.45.123.67
Require ip 10.0.0.0/8
</Directory>
# Или в .htaccess файле внутри защищаемой директории:
<RequireAny>
Require ip 192.168.1.100
Require ip 78.45.123.67
Require ip 10.0.0.0/8
</RequireAny>
# Если нужно запретить конкретный IP и всем остальным разрешить:
<RequireAll>
Require all granted
Require not ip 185.220.101.45
</RequireAll>
Старый синтаксис Apache 2.2 (Order, Allow, Deny) официально устарел, но ещё встречается на хостингах с PHP 7.4 и старыми дистрибутивами. Если у вас Apache 2.2 — пора менять хостинг, серьёзно. PHP 7.4 уже давно не получает обновлений безопасности, и это отдельная большая проблема.
Один нюанс, который меня однажды подловил: если у вас WordPress на Apache и вы добавляете ограничение по IP в .htaccess в папке wp-admin, убедитесь, что это не конфликтует с основным .htaccess WordPress. Лучше создать отдельный .htaccess именно в папке wp-admin. Кстати, про защиту wp-admin я подробно разбирал в отдельной статье — там есть и другие методы помимо IP-фильтрации: защита wp-admin: закрываем доступ к панели WordPress.
Ограничение по IP на уровне PHP
Иногда у вас нет доступа к конфигурации nginx или Apache. Или нужно более гибкое поведение — например, разные уровни доступа для разных IP. В таких случаях IP-фильтрацию можно реализовать прямо в PHP.
Я использую этот подход для защиты отдельных скриптов в проектах на Laravel и для кастомных решений на Bitrix, когда нужна логика сложнее, чем просто allow/deny:
<?php
class IpAccessControl
{
private array $allowedRanges = [
'192.168.1.0/24',
'78.45.123.67/32',
'10.0.0.0/8',
];
public function getClientIp(): string
{
// Важно: не доверяйте X-Forwarded-For без проверки!
// Используйте только если знаете, что стоит доверенный прокси
$trustedProxies = ['127.0.0.1', '10.0.0.1']; // ваши прокси-серверы
if (
isset($_SERVER['HTTP_X_FORWARDED_FOR'])
&& in_array($_SERVER['REMOTE_ADDR'], $trustedProxies)
) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ips[0]);
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
public function isAllowed(string $ip): bool
{
foreach ($this->allowedRanges as $range) {
if ($this->ipInRange($ip, $range)) {
return true;
}
}
return false;
}
private function ipInRange(string $ip, string $range): bool
{
[$network, $mask] = explode('/', $range);
$maskBits = (int) $mask;
$ipLong = ip2long($ip);
$networkLong = ip2long($network);
if ($ipLong === false || $networkLong === false) {
return false;
}
$wildcardMask = (1 << (32 - $maskBits)) - 1;
$netMask = ~$wildcardMask;
return ($ipLong & $netMask) === ($networkLong & $netMask);
}
public function denyIfNotAllowed(): void
{
$ip = $this->getClientIp();
if (!$this->isAllowed($ip)) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['error' => 'Access denied']);
exit;
}
}
}
// Использование:
$access = new IpAccessControl();
$access->denyIfNotAllowed();
// Дальше — ваш защищённый код
Важный момент с X-Forwarded-For: это поле легко подделать. Если ваш сайт стоит за CloudFlare, nginx-прокси или load balancer, реальный IP клиента приходит именно в этом заголовке. Но если принимать его без проверки, злоумышленник может подставить любой IP и обойти фильтр. Поэтому я проверяю REMOTE_ADDR — если это доверенный прокси, тогда смотрю на X-Forwarded-For. Если нет — использую только REMOTE_ADDR.
На PHP 8.1+ я бы ещё добавил строгую типизацию (declare(strict_types=1)) и обработку IPv6, но для большинства практических задач кода выше достаточно.
ip2long() работает только с IPv4. Для IPv6 используйте inet_pton() и inet_ntop().IP-фильтрация в WordPress и Битрикс
Отдельно остановлюсь на CMS-специфичных решениях, потому что у каждой платформы есть свои нюансы.
WordPress
В WordPress есть несколько уровней, где можно применить IP-фильтрацию. Первый — уже упомянутый .htaccess для wp-admin. Второй — плагины безопасности. Я обычно рекомендую Wordfence (бесплатная версия покрывает большинство нужд) или WP Cerber. Оба умеют блокировать IP автоматически при подозрительной активности и позволяют добавлять IP в белые/чёрные списки вручную.
Третий вариант — через functions.php или отдельный must-use плагин. Это даёт максимальный контроль:
<?php
// В файле /wp-content/mu-plugins/ip-restriction.php
add_action('init', function () {
$restricted_paths = ['/wp-admin/', '/wp-login.php'];
$allowed_ips = ['192.168.1.100', '78.45.123.67'];
$current_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$client_ip = $_SERVER['REMOTE_ADDR'];
foreach ($restricted_paths as $path) {
if (str_starts_with($current_path, $path)) {
if (!in_array($client_ip, $allowed_ips, true)) {
status_header(403);
wp_die(
'Доступ запрещён.',
'Доступ запрещён',
['response' => 403]
);
}
}
}
});
Must-use плагины (папка mu-plugins) хороши тем, что их нельзя случайно отключить через панель WordPress. Это важно для критических ограничений безопасности.
Битрикс
В Битриксе есть встроенный WAF (Web Application Firewall) в модуле «Проактивная защита». Там можно настроить как блокировку IP, так и автоматические правила. Но честно говоря, встроенный WAF Битрикса — это не замена нормальной конфигурации nginx. Я всегда настраиваю оба уровня.
Для ограничения доступа к /bitrix/admin/ на уровне Битрикса можно использовать файл /bitrix/.htaccess (если Apache) или добавить правила в nginx-конфиг виртуального хоста. Второй вариант предпочтительнее — он отрабатывает раньше, до PHP.
Ещё один подход в Битриксе — использование событий модуля main. Можно повесить обработчик на OnBeforeProlog и проверять IP там. Но это уже оверкилл для большинства задач.
Закрытие staging и dev-окружений
Это, пожалуй, самый частый сценарий, с которым я работаю. У меня есть правило: любой staging или dev-сервер должен быть закрыт по IP с первого дня. Без исключений.
Был случай с клиентом — разрабатывали новую версию корпоративного сайта. Dev-окружение на поддомене dev.company.ru висело открытым несколько месяцев. В итоге Яндекс проиндексировал его, и когда мы запустили основной сайт, получили дублированный контент и просадку позиций. Неприятно, хотя и решаемо. Но зачем создавать себе лишние проблемы?
Мой стандартный конфиг для staging на nginx выглядит так:
server {
listen 443 ssl http2;
server_name staging.mysite.ru;
# SSL-настройки
ssl_certificate /etc/letsencrypt/live/staging.mysite.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/staging.mysite.ru/privkey.pem;
# IP-ограничения
allow 192.168.1.0/24; # офис
allow 78.45.123.67; # разработчик 1
allow 91.200.14.33; # разработчик 2
allow 185.220.0.0/16; # CI/CD сервер
deny all;
# Дополнительно — базовая авторизация как второй слой
auth_basic "Staging Environment";
auth_basic_user_file /etc/nginx/.htpasswd_staging;
root /var/www/staging/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
}
Обратите внимание: я добавляю сюда ещё и базовую HTTP-авторизацию поверх IP-фильтрации. Это называется "defence in depth" — эшелонированная защита. Если кто-то каким-то образом попадёт в разрешённую подсеть (например, скомпрометирует офисную сеть), его ещё остановит пароль. Для staging это не паранойя, а нормальная практика.
https://api.github.com/meta.Ограничение доступа к API-эндпоинтам
API — отдельная история. Здесь IP-фильтрация особенно актуальна для внутренних эндпоинтов, которые вызываются только между вашими собственными сервисами. Если у вас микросервисная архитектура или просто несколько серверов, которые общаются между собой — закрывайте внутренние API по IP однозначно.
По опыту работы с Laravel-проектами: если у вас есть эндпоинты типа /api/internal/* или /api/cron/*, которые вызываются только с вашего же сервера или из вашей инфраструктуры — они не должны быть доступны из интернета. Никакой авторизации не нужно, если IP уже отфильтрован на уровне nginx.
Для внешних API, которые вызывают партнёры с известных IP — та же история. Получил от партнёра список их IP-адресов, добавил в белый список, и теперь не нужно думать о том, что кто-то посторонний будет дёргать ваш API. Это не отменяет авторизацию через токены, но снижает поверхность атаки. Про настройку firewall в целом я рекомендую прочитать отдельную статью — там много деталей про iptables и nftables: настройка Firewall для защиты сайта.
Подводные камни и типичные ошибки
За годы работы я набил достаточно шишек, чтобы составить список типичных ошибок при настройке IP-фильтрации.
Ошибка 1: Забыть про IPv6. Если вы разрешаете 192.168.1.100, но пользователь подключается с ::1 (IPv6 loopback) или с IPv6-адресом вашего офиса — он получит 403. Всегда добавляйте оба варианта адреса, если сеть поддерживает IPv6.
Ошибка 2: Не проверить заголовки прокси. Если перед вашим nginx стоит CloudFlare, AWS CloudFront или любой другой CDN/прокси, в REMOTE_ADDR вы будете видеть IP прокси-сервера, а не реального клиента. Нужно настроить set_real_ip_from в nginx, чтобы он правильно определял клиентский IP из заголовков CloudFlare. Это критически важно.
Ошибка 3: Заблокировать поисковых роботов. Если вы закрываете весь сайт по IP (например, при переезде), не забудьте либо оставить доступ для Googlebot/Yandexbot, либо вернуть 503 вместо 403. Поисковики понимают 503 как "временно недоступно" и не выбрасывают страницы из индекса, а 403 — как "запрещено", и могут начать деиндексацию.
Ошибка 4: Хранить список IP в коде. Особенно в WordPress и Bitrix я видел случаи, когда IP-адреса вшивались прямо в файлы плагинов или в init.php Битрикса. Это плохая идея. Меняется IP — нужно лезть в код. Лучше хранить в конфиге nginx, в переменных окружения или в базе данных с удобным интерфейсом управления.
Ошибка 5: Отсутствие документации. Грубо говоря, через полгода вы сами не вспомните, почему именно эти IP в белом списке и что будет, если их удалить. Всегда комментируйте правила — кому принадлежит каждый IP, зачем он добавлен, когда истекает (если это временный доступ).
Ошибка 6: Нет резервного доступа. Заблокировали себя? Такое бывает. Всегда держите возможность подключиться через VPN или через консоль хостинга/VPS (KVM-консоль, iLO, IPMI). Иначе в случае ошибки вы окажетесь заперты снаружи собственного сервера.
Rate limiting и fail2ban как дополнение к IP-фильтрации
IP-фильтрация — это статичный инструмент. Вы заранее знаете, кого пускать. Но что делать с незнакомыми IP, которые ведут себя подозрительно? Здесь на помощь приходят динамические инструменты.
Fail2ban я использую на всех серверах. Он мониторит логи nginx, Apache, SSH и других сервисов, и автоматически добавляет в iptables блокировку для IP, которые превысили порог ошибок. Например, если с одного IP пришло 10 запросов на /wp-login.php за 60 секунд — он автоматически блокируется на час. Очень эффективно против брутфорса.
Rate limiting в nginx — ещё один уровень. Ограничиваем количество запросов в единицу времени для конкретных URL. Про это я подробно писал в статье настройка rate limiting для защиты от DDoS — рекомендую почитать в связке с этой статьёй.
Комбинация статичной IP-фильтрации (белые/чёрные списки) + динамической блокировки через fail2ban + rate limiting в nginx — это то, что я настраиваю на каждом production-сервере. Эти три инструмента дополняют друг друга и создают нормальный базовый уровень защиты.
VPN как альтернатива IP-фильтрации
Финальная мысль, которую я всегда озвучиваю клиентам: в некоторых случаях VPN удобнее, чем жёсткая IP-фильтрация.
Если у вас команда из 10 разработчиков, которые работают из разных мест — кафе, коворкинги, домашние сети, командировки — поддерживать актуальный список IP становится мучением. Новый сотрудник, смена провайдера, поездка в другой город — всё это требует обновления правил. Это административная нагрузка.
Решение: поднимаете WireGuard или OpenVPN-сервер, все сотрудники подключаются через него, и вы разрешаете доступ только с IP этого VPN-сервера. Один IP в белом списке, а управление доступом переносится в VPN — там своя авторизация, можно отзывать доступ, вести аудит подключений.
WireGuard в 2026 году — это мой выбор по умолчанию. Быстрее OpenVPN, проще в настройке, меньше кода = меньше уязвимостей. Настраивается за 20 минут, работает стабильно.
Но и у VPN-подхода есть минус: если VPN-сервер ляжет, вся команда потеряет доступ к защищённым ресурсам. Поэтому для критических систем я делаю комбинацию: основной доступ через VPN + резервный доступ с конкретного "аварийного" IP (например, IP офисного сервера), который прописан явно.
Если вас интересует комплексная настройка безопасности сервера и сайта — посмотрите на поддержку Битрикс-проектов или поддержку WordPress-сайтов, в рамках которых я в том числе провожу аудит и настройку безопасности. А если нужна разовая доработка конкретного функционала — это на страницу доработки сайта.
IP-фильтрация — простой инструмент, но именно простые инструменты, правильно настроенные и встроенные в систему, дают наибольший эффект. Главное — не относиться к ней как к серебряной пуле. Это один слой из нескольких, и работает он хорошо только в комбинации с другими мерами безопасности.
Нужно настроить защиту сайта по IP-адресам?
Наши специалисты настроят ограничение доступа по IP быстро и безопасно — оставьте заявку прямо сейчас.