Изоляция пользователей VPN друг от друга
Один VPN-сервер на несколько человек — обычная экономия: не поднимать отдельный VPS каждому сотруднику или клиенту. Но по умолчанию все, кто подключился к серверу, попадают в общую подсеть и часто видят друг друга — сканируют порты, стучатся по SMB, а если у кого-то заражённый ноутбук, зловред пытается расползтись по соседям. Ниже — как закрыть эту дыру на WireGuard и OpenVPN, не сломав при этом доступ клиентов в интернет и к серверу.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему трафик между клиентами — это риск
Когда VPN поднимают «для доступа в интернет» или «для обхода блокировок», о клиент-клиентском трафике обычно не думают вообще — какая разница, если все свои. Проблема начинается, когда сервер общий не для одного человека, а для команды, семьи или клиентов хостера:
- Разные уровни доверия. Домашний ноутбук родственника и рабочий сервер бухгалтерии не должны быть в одной L3-сети только потому, что оба ходят через один VPN.
- Боковое перемещение при компрометации. Заражённая машина в общей подсети VPN может сканировать соседей (
nmap -sn 10.8.0.0/24), искать открытые SMB/RDP/базы данных и атаковать их напрямую, минуя интернет-периметр каждого клиента. - Мультитенантность. Если вы предоставляете VPN нескольким клиентам с одного сервера (агентство, небольшой хостинг-провайдер, коворкинг), клиент-клиентский трафик — это утечка изоляции между заказчиками, которая не прощается на аудите.
- Требования комплаенса. Некоторые политики (PCI DSS, внутренние ИБ-регламенты) прямо требуют, чтобы удалённые пользователи не имели прямой сетевой видимости друг друга, только доступ к разрешённым ресурсам.
Решение простое по формулировке — запретить трафик «клиент → клиент» и оставить только «клиент → сервер/интернет» и, при необходимости, «клиент → конкретный внутренний сервис». Сложность в деталях: WireGuard и OpenVPN устроены по-разному, и наивная настройка часто не работает так, как кажется.
Как устроена маршрутизация внутри VPN-сервера
VPN-сервер — это, по сути, роутер: у него один туннельный интерфейс (wg0 или tun0), к которому подключены все клиенты с адресами из общей подсети, например 10.8.0.0/24. Когда пакет от клиента A с адресом назначения клиента B попадает на сервер, дальше происходит одно из двух:
- Ядро маршрутизирует пакет обратно в тот же туннельный интерфейс — это и есть межклиентский трафик. Он либо разрешён (по умолчанию, если ничего не настраивать и включён
net.ipv4.ip_forward=1), либо блокируется правилом firewall в цепочкеFORWARD. - OpenVPN обрабатывает пакет внутри собственного процесса, минуя обычную маршрутизацию ядра — это происходит только при явно включённой директиве
client-to-client, и о ней ниже отдельно, потому что она ломает привычную логику iptables.
WireGuard такой «внутренней» логики не имеет — у него нет отдельного режима client-to-client, всё решается на уровне AllowedIPs и обычного forwarding в ядре. Это делает WireGuard немного проще для изоляции, но и здесь по умолчанию ничего не заблокировано: если вы просто следовали стандартной установке из статьи про настройку WireGuard на VPS, клиенты уже видят друг друга.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверИзоляция в WireGuard: правило на интерфейсе
В WireGuard клиент-клиентский трафик — это пакет, который вошёл через wg0 и должен выйти через wg0 же (в отличие от трафика в интернет, который выходит через eth0/ens3). Значит, достаточно запретить в цепочке FORWARD пересылку пакетов между одним и тем же интерфейсом.
На nftables (актуальный вариант на конец 2026 года для Debian 12/Ubuntu 24.04):
table inet filter {
chain forward {
type filter hook forward priority 0; policy accept;
# Разрешаем трафик клиент -> интернет и клиент -> сервер
iifname "wg0" oifname "eth0" accept
iifname "eth0" oifname "wg0" accept
# Запрещаем клиентам ходить друг к другу через wg0
iifname "wg0" oifname "wg0" drop
}
}
Если сервер использует iptables-legacy или nftables в режиме совместимости, тот же результат так:
iptables -A FORWARD -i wg0 -o wg0 -j DROP
iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
Важно: правило DROP для wg0 -> wg0 должно стоять выше любых широких ACCEPT-правил, иначе оно просто не сработает — iptables/nftables идут по правилам последовательно и останавливаются на первом совпадении в chain с политикой accept по умолчанию. Проверить порядок:
iptables -L FORWARD -v -n --line-numbers
Если вы используете NAT-маскарадинг для выхода клиентов в интернет (iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE), эта настройка не конфликтует с изоляцией — маскарадинг работает в таблице nat, а блокировка клиент-клиент — в filter/FORWARD, это разные этапы обработки пакета.
Изоляция в OpenVPN: ловушка client-to-client
Здесь важно не спутать причину и следствие. В конфиге OpenVPN есть директива:
client-to-client
Многие инструкции советуют «просто не включать её» — и это отчасти верно, но с нюансом, который стоит понимать, а не просто скопировать. Без client-to-client сервер не маршрутизирует трафик между клиентами на уровне своего процесса — пакеты для другого клиента отправляются в TUN-интерфейс и дальше обрабатываются обычным IP-стеком ядра, то есть попадают в цепочку FORWARD, где их можно фильтровать iptables/nftables — точно так же, как в примере с WireGuard выше:
iptables -A FORWARD -i tun0 -o tun0 -j DROP
iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o tun0 -m state --state ESTABLISHED,RELATED -j ACCEPT
А вот если client-to-client включена, OpenVPN пересылает трафик между клиентами внутри собственного процесса, минуя стандартную маршрутизацию ядра — и в этом случае правила iptables на FORWARD его не видят и не блокируют, потому что пакет физически не проходит через сетевой стек ядра тем путём, который фильтрует firewall. Это прямо описано в документации OpenVPN и регулярно ломает голову тем, кто ставит DROP-правило и не понимает, почему клиенты всё равно видят друг друга.
Правильная связка для изоляции на OpenVPN:
- Не включать
client-to-clientв серверном конфиге вообще. - Держать
net.ipv4.ip_forward=1(без него клиенты не попадут и в интернет). - Добавить
DROP-правило наtun0 -> tun0вFORWARD, как выше.
Если вы настраивали сервер по инструкции OpenVPN на VPS или используете мультипользовательскую схему с сертификатами Easy-RSA, проверьте серверный .conf на строку client-to-client — если она там есть «на всякий случай», уберите и перезапустите службу.
Проверка изоляции: как убедиться, что она работает
Правило в конфиге ничего не значит, пока вы не проверили его на двух реальных клиентах.
Шаг 1 — узнайте VPN-адреса двух клиентов. Для WireGuard — wg show, для OpenVPN — cat /var/log/openvpn/status.log или management-интерфейс.
Шаг 2 — с клиента A попробуйте пинговать клиента B:
ping -c 4 10.8.0.3
Ожидаемый результат при рабочей изоляции — 100% потерь (Destination Host Unreachable или таймаут), при этом ping до самого сервера и до внешних адресов (например, 1.1.1.1) должен проходить нормально.
Шаг 3 — проверьте не только ICMP, но и TCP/UDP. Некоторые окружения фильтруют ICMP отдельно, и «пинг не проходит» ещё не значит, что порты закрыты:
nmap -p 1-1000 10.8.0.3
Если изоляция настроена верно, nmap с клиента A должен получить filtered или таймаут по всем портам клиента B, а не список открытых сервисов.
Шаг 4 — посмотрите на сервере, что реально дропается, добавив временный лог-таргет перед DROP:
iptables -I FORWARD -i wg0 -o wg0 -j LOG --log-prefix "C2C-DROP: "
journalctl -k -f | grep C2C-DROP
Это особенно полезно, если у вас несколько VPN-подсетей или несколько интерфейсов (wg0, wg1) — легко забыть повторить правило для второго.
Частичный доступ: сервис на сервере разрешён, клиенты друг другу — нет
Часто нужна не полная изоляция, а промежуточный вариант: у всех клиентов есть общий сервис на самом VPN-сервере (файлхранилище, внутренний DNS, панель мониторинга), но клиенты друг к другу напрямую ходить не должны. Здесь важно не путать «трафик к серверу» и «трафик между клиентами через wg0/tun0» — это разные направления, и правило iifname "wg0" oifname "wg0" drop их не затрагивает, потому что трафик к самому серверу обрабатывается в цепочке INPUT, а не FORWARD.
Если нужно тоньше — разрешить клиентам только конкретный порт друг у друга (например, общий сервис синхронизации на 51820), а остальное запретить, правило переписывается точечно:
# Разрешить доступ к общему сервису на 10.8.0.1:8080 внутри VPN-сети
iptables -A FORWARD -i wg0 -o wg0 -d 10.8.0.1 -p tcp --dport 8080 -j ACCEPT
# Всё остальное клиент-клиент — запретить
iptables -A FORWARD -i wg0 -o wg0 -j DROP
Если общий сервис живёт в Docker-контейнере на том же хосте, добавляется ещё один слой изоляции — правила Docker-сети и iptables могут конфликтовать по порядку цепочек, это подробно разобрано в статье про изоляцию сервисов через Docker: Docker сам добавляет правила в DOCKER-USER, и ваши правила изоляции VPN стоит держать в отдельной, явно проверяемой цепочке, чтобы обновление Docker их не затёрло.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Изоляция клиентов замедляет VPN?
Нет, лишние правила firewall на несколько записей практически не влияют на производительность — накладные расходы на сопоставление правил в netfilter измеряются микросекундами, это не бутылочное горлышко по сравнению с самим шифрованием.
Нужно ли отдельно настраивать IPv6?
Да, если сервер выдаёт клиентам IPv6-адреса (некоторые провайдеры это делают по умолчанию), правило нужно продублировать в ip6tables или в nftables в таблице inet — она уже покрывает оба стека, что удобнее.
Правила переживут перезагрузку сервера?
Сами по себе — нет. Для iptables нужен iptables-persistent (Debian/Ubuntu) с netfilter-persistent save, для nftables — сохранённый файл конфигурации, подключённый через systemctl enable nftables и загружаемый при старте.
Можно ли изолировать клиентов только для части пользователей, а остальным оставить доступ друг к другу?
Да, через выделение подсетей: одной группе клиентов назначить 10.8.1.0/24, другой — 10.8.2.0/24, и фильтровать по source/destination конкретных диапазонов, а не по всему интерфейсу целиком.
Как это сочетается с двухфакторной аутентификацией на VPN?
Никак не пересекается технически — изоляция трафика и двухфакторная аутентификация решают разные задачи: одна не даёт зайти чужому, вторая не даёт зашедшим видеть друг друга. Для серьёзной защиты имеет смысл использовать оба слоя одновременно.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →