На практике проблема выглядит одинаково: вы аккуратно закрыли админку по IPv4 (через allow/deny в Nginx, Require ip в Apache, firewall или ACL у провайдера), проверили со своего адреса — всё ок. А потом внезапно выясняется, что админ-панель или служебный endpoint доступен «из любого места».
Очень часто причина — IPv6. Сервер работает в dual stack (одновременно слушает IPv4 и IPv6), а правила доступа настроены только для IPv4. В результате по IPv4 всё закрыто, а по IPv6 — открыто. Особенно опасный маркер в логике ACL — диапазон ::/0: это «весь IPv6‑интернет», аналог 0.0.0.0/0 для IPv4.
Почему IPv6 ломает привычную модель доступа
Когда вы пишете ограничения «по IP», важно понимать: они применяются к тому стеку, через который пришло соединение. Если сервис слушает listen [::]:443 (или бэкенд торчит на [::]:8080), то IPv6‑клиенты пойдут по IPv6 и будут проверяться по IPv6‑правилам.
Если IPv6‑правил нет вовсе, вы можете получить эффект «по IPv4 закрыто, по IPv6 открыто». Это особенно больно на VPS/VDS: IPv6 зачастую выдают «сразу», и он реально публичный, даже если вы его не планировали использовать.
Коротко: dual stack требует двойной проверки: и веб‑ACL (Nginx/Apache), и сетевой фильтрации должны учитывать IPv6 так же, как IPv4.
Что означает ::/0 и как его ошибочно получают
::/0 — это префикс «все адреса IPv6». Его легко встретить в дефолтных маршрутах и так же легко случайно «протащить» в разрешающие правила, когда кто-то временно «открыл всем», а потом забыл откатить.
Где чаще всего ломается логика доступа:
- Nginx: ограничения прописали в одном
location, а доступ есть через другой (более специфичный блок, альтернативный URL или отдельныйserver); либо забыли, что фронт слушаетlisten [::]:443. - Apache: смешали старый синтаксис контроля доступа с новым или неверно собрали логику
Requireпри переходе на современную конфигурацию. - Firewall: закрыли IPv4 через
iptables, а IPv6 стек не трогали (нет правилip6tablesили не используется единаяinet-таблица вnftables). - Reverse proxy: защитили только edge‑прокси, но бэкенд слушает на публичном IPv6 и доступен напрямую, обходя все ACL на прокси.

Диагностика: как быстро понять, открыт ли сервис по IPv6
1) Проверяем, слушает ли сервис IPv6
На сервере начните с сокетов:
ss -lntp
Ищите [::]:443, [::]:80, [::]:8443 и т.п. Если они есть — сервис принимает соединения по IPv6.
2) Проверяем доступ снаружи именно по IPv6
Нужен внешний хост с IPv6 (другая машина, jump‑host, мониторинг). Проверяйте явно ключевые URL:
curl -6 -I your-domain.example
curl -6 -I your-domain.example/admin/
Если там, где ожидается 403 (или редирект на заглушку), вы получаете 200/302 — ограничения по IPv6 не работают.
3) Смотрим, какой адрес видит веб‑сервер и приложение
- в Nginx access log обычно ориентируются на
$remote_addr; - в Apache — на
%aили%h(зависит от формата).
Если вы видите в логах IPv6‑адреса клиентов, значит трафик реально приходит по IPv6 и все ACL должны учитывать этот стек.
Nginx: allow/deny для IPv6 и типовые ловушки
В Nginx директивы allow/deny корректно работают и с IPv4, и с IPv6. Ошибки обычно не в синтаксисе, а в контексте: ограничение повесили не на тот location, забыли про более специфичный location, или у вас есть второй server, где ACL отсутствует.
Базовый пример: закрываем /admin для IPv4 и IPv6
location ^~ /admin/ {
allow 192.0.2.10;
allow 198.51.100.0/24;
allow 2001:db8:1234:10::/64;
deny all;
proxy_pass http://backend;
}
deny all; перекрывает оба стека. Важно, чтобы этот location реально покрывал все пути админки.
Нюанс: real_ip за прокси/CDN
Если вы восстанавливаете реальный адрес клиента через real_ip_header и set_real_ip_from, то allow/deny будет проверять уже «подменённый» IP (обычно это и нужно). Но список доверенных прокси должен включать и IPv6 тоже, иначе логика легко «съезжает».
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24;
set_real_ip_from 2001:db8:feed::/48;
После включения real_ip заново тестируйте админку и по curl -4, и по curl -6.
Частая ошибка: «закрыли location, но забыли закрыть весь server»
Если админка вынесена на отдельный hostname (например, admin.example), безопаснее закрывать доступ на уровне всего server. Так меньше шансов, что появится «обходной» URL.
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name admin.example;
allow 192.0.2.10;
allow 2001:db8:1234:10::/64;
deny all;
location / {
proxy_pass http://backend_admin;
}
}
Apache: Require ip для IPv6 и корректная логика доступа
В Apache современная схема контроля доступа — директивы Require (модуль mod_authz_core). IPv6 указывается напрямую, включая CIDR‑префиксы.
Пример: закрываем /admin по IPv4 и IPv6
<Location "/admin">
Require ip 192.0.2.10
Require ip 198.51.100.0/24
Require ip 2001:db8:1234:10::/64
</Location>
Несколько строк Require ip работают как логическое «ИЛИ» (достаточно совпасть с одним правилом). Для более сложной логики используйте <RequireAll>, <RequireAny>, <RequireNone>, но только когда реально нужно.
Нюанс reverse proxy: реальный IP через mod_remoteip
Если Apache стоит за reverse proxy, то без настройки восстановления адреса клиента Require ip будет видеть IP прокси и правила по клиентскому IP станут бессмысленными.
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 203.0.113.0/24
RemoteIPTrustedProxy 2001:db8:feed::/48
Дальше сверяйте логи и поведение ACL: адрес в логах должен совпадать с тем, по которому вы строите allowlist.
IPv6 firewall: почему «iptables настроен» не значит «всё закрыто»
Исторически iptables — это IPv4, а IPv6 живёт отдельно (через ip6tables). Чтобы не держать два набора правил, удобнее перейти на nftables и таблицу inet, которая покрывает оба стека.
Если вы часто обслуживаете сайты на VDS, это особенно актуально: IPv6 может быть включён и доступен извне даже тогда, когда вы «про него не думали», а значит сетевой фильтр должен быть dual stack по умолчанию.
Шаблон nftables: 80/443 всем, админка только из allowlist (IPv4+IPv6)
Ниже — именно шаблон логики. Порты и сети подставьте свои. Перед применением убедитесь, что у вас есть доступ к серверу по SSH и что правила не отрежут вас от управления.
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0 ; policy drop ; }
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input iif lo accept
nft add rule inet filter input ip protocol icmp accept
nft add rule inet filter input ip6 nexthdr icmpv6 accept
nft add rule inet filter input tcp dport { 80, 443 } accept
nft add rule inet filter input tcp dport 8443 ip saddr { 192.0.2.10, 198.51.100.0/24 } accept
nft add rule inet filter input tcp dport 8443 ip6 saddr { 2001:db8:1234:10::/64 } accept
ICMPv6 не стоит «просто вырубать»: он критичен для нормальной работы IPv6 (PMTUD, Neighbor Discovery). Жёсткая блокировка ICMPv6 часто приводит к странным таймаутам и «плавающим» проблемам доступности.
Если админка доступна по HTTPS, не забывайте про корректный сертификат и цепочку доверия: для публичных панелей удобнее заранее оформить SSL-сертификаты и держать автопродление под контролем.

Reverse proxy и ACL: где именно должен жить запрет
Есть два слоя защиты, и лучше применять оба:
- На edge (reverse proxy): блокируем доступ к админке на входе, до передачи запроса на бэкенд.
- На бэкенде/приложении: убеждаемся, что к админке нельзя достучаться обходным путём (напрямую по IP/порту, через второй домен, через сервисный интерфейс).
Типовая архитектурная ошибка: прокси стоит снаружи, а бэкенд слушает на [::]:8080 или [::]:9000 «для удобства». Если firewall для IPv6 не настроен, этот порт становится доступен всему интернету, и любой ACL в прокси обходится прямым обращением к бэкенду.
Правило №1: бэкенд слушает только локально
Если это возможно, поднимайте апстрим на loopback. Например:
proxy_pass http://127.0.0.1:8080;
Для сервисов, которые умеют слушать IPv6, осознанно выбирайте адрес: 127.0.0.1, ::1 или приватная сеть. Не оставляйте «везде» по умолчанию.
Правило №2: если нужен доступ извне — закрывайте порт firewall-ом и ACL
Например, панель управления на нестандартном порту: даже если вы закрыли её Nginx/Apache, дополнительно ограничьте вход на уровне firewall по allowlist.
Чек-лист: как не получить «ACL ::/0» в продакшене
- Проверьте слушающие сокеты: наличие
[::]:*означает, что IPv6 включён и должен быть учтён в правилах. - Nginx: добавляйте IPv6‑сети рядом с IPv4 и завершайте защищённые блоки
deny all;. - Apache: используйте
Require ipс IPv6‑префиксами и настройтеmod_remoteipза прокси. - Firewall: используйте
nftablesтаблицуinetили не забывайте отдельные правила для IPv6. - Reverse proxy: проверьте, что бэкенд не торчит наружу по IPv6 на сервисных портах.
- Тестируйте явно: выполняйте проверки
curl -4иcurl -6для критичных URL, особенно для админки.
Типовые симптомы в логах и как их интерпретировать
Когда IPv6 «обходит» ограничения, обычно виден один из паттернов:
- в логах прокси админка регулярно запрашивается с разных IPv6‑адресов, хотя по IPv4 заметного сканирования нет;
- в логах приложения клиент — это адрес прокси, а не реальный пользователь (значит ACL по IP на уровне приложения не работает);
- порт бэкенда доступен из интернета по IPv6 (есть попытки логина/сканирование), хотя по IPv4 он закрыт.
В таких случаях правильный путь — не «подкрутить allow/deny ещё раз», а пройтись по слоям: слушающие адреса, firewall dual stack, доверие к заголовкам, и только потом правила в веб‑сервере. Дополнительно полезно держать базовую гигиену HTTP: см. статью про защитные HTTP-заголовки в Nginx и Apache.
Итог: безопасный dual stack без сюрпризов
IPv6 сам по себе не «дырявый» — но он легко ломает привычные ожидания, если инфраструктура исторически настроена под IPv4. Как только вы включили dual stack, считайте обязательным обновить и проверить весь контур доступа: reverse proxy ACL, конфигурации Nginx/Apache и сетевой firewall.
Относитесь к ::/0 так же, как к 0.0.0.0/0: это весь интернет. А значит, его появление в разрешающих правилах — всегда повод остановиться и перепроверить конфиг.


