• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Псевдо double‑VPN и OPSEC‑практики

stryx

floppy-диск
Пользователь
Регистрация
17.01.2026
Сообщения
9
Реакции
10
В современном мире вопрос VPN или прокси давно не про удобство. Это инженерная дисциплина управления поверхностью атаки, минимизации сигнатур, защиты от корреляции идентификаторов и оценки юрисдикционных рисков. Любая схема должна строиться как маленькая система OPSEC: что мы скрываем, от кого, какими средствами и какой ценой. Магии здесь нет — только грамотное планирование и понимание ограничений.
Далее я разбираю рабочую цепочку туннелирования, со своей колокольни, где Xray, Gluetun, и Docker работают как единая система.
Цель — снизить детектируемость, усложнить корреляцию трафика и изолировать выходную точку. Иллюзий абсолютной анонимности здесь нет: задача — повысить стоимость анализа и атак, а не стать сверх 'Анонимным'.
Псевдо double‑VPN
Эта схема — не чистый double‑hop вроде Tor, но и не иллюзия. Входной‑VPS видит исходный IP клиента, VPN‑Выход видит интернет‑трафик, но не знает о начальном клиенте.Прямой корреляции между ними нет без кооперации минимум двух юрисдикций.
Важно корректно понимать модель контроля. Формально цепочка не принадлежит одному юридическому субъекту: VPS‑хостинг, VPN‑провайдер и оператор находятся в разных странах. Однако логический контроль остаётся у оператора: он управляет VPS, выбирает VPN‑провайдера и контролирует клиент. Это не trustless‑модель, а модель разделения зон видимости.
Такая схема ломает привычные DPI‑сигнатуры, снижает репутационные риски IP и усложняет корреляцию. Но компрометация клиента или сервера VPS по‑прежнему ломает весь OPSEC. Тайминговая корреляция, утечки логов или ошибки конфигурации мгновенно уничтожают невидимость.
0. Принципиальный момент
Использование root как повседневного пользователя системы — одна из самых частых и самых дорогих ошибок. Root должен рассматриваться как аварийный режим, а не рабочая среда.
Любая уязвимость в Docker, x‑ui, Xray или вспомогательных скриптах, запущенных под root, автоматически превращается в полную компрометацию хоста. OPSEC логика здесь простая: если сервис взломан, он не должен мгновенно становиться root.
Поэтому вся эксплуатация — Docker, x‑ui, конфиги, обновления — выносится в отдельного пользователя. Root остаётся только для bootstrap и экстренных действий.


1. Подготовка хоста: базовая гигиена VPS
Выбираем образ Debian 12 в Европе. Никаких предустановленных панелей, "VPN‑скриптов" и авто‑харденинга от хостера. Каждый лишний пакет — потенциальная точка утечки, каждый сервис — коридор для lateral movement.
Обновляем систему:
Bash:
apt update && apt full-upgrade -y
Устанавливаем Docker и docker‑compose:
Bash:
apt install -y sudo ufw docker.io docker-compose && systemctl enable docker
Docker здесь — не про удобство, а про контроль сетевых пространств, предсказуемость маршрутов и жёсткую привязку сервисов к VPN‑OUT.
1.1 Создание операционного пользователя
Создаём отдельного пользователя, под которым будет жить вся инфраструктура:
Bash:
adduser ops
usermod -aG sudo,docker ops
newgrp docker
Всю дальнейшую работу ведём только под ops. Root‑логин по SSH отключается полностью. Это резко снижает радиус поражения при компрометации контейнера или панели.
2. UFW: минимизация сетевой поверхности
Firewall — первый OPSEC‑барьер. Не является надёжной защитой, но минимизирует вероятность ошибок настройки.
Разрешаем только необходимое:
Bash:
ufw default deny incoming
Bash:
ufw default allow outgoing
Bash:
ufw allow 22 && ufw allow 443 && ufw allow 53 && ufw enable
Firewall здесь — страховка от случайного лишних пакетов из глобальной сети.
3. Fail2ban
Fail2ban убирает фоновый мусор и сканеры:
Bash:
apt install -y fail2ban
 systemctl enable fail2ban
 systemctl start fail2ban
Минимальные jails:
  • SSH
  • x‑ui (если панель вообще слушает порт наружу)
4. Gluetun
Gluetun выполняет ключевую роль:
  • Поднимает VPN‑туннель
  • Реализует killswitch на уровне iptables
  • Контролирует DNS
  • Изолирует сетевое пространство контейнеров
SERVER_REGIONS выбираются по юрисдикции и OPSEC, а не по ping:
Bash:
Switzerland,Iceland,Finland,Czechia,Romania,Bulgaria,Serbia,Hungary,Slovenia,Australia
А регулярное обновление списка серверов с помощью встроенного функционала, снижает вероятность использования "мертвых" exit‑нодов, что в итоге ускоряет процесс рестарта и смены выходных нод.
Glue кстати,частенько любит поделать мозги с подключением к vpn, причины он и сам не всегда понимает.
Конечный docker-compose.yml
предполагается размещение в /home/ops/, а не в /root
Код:
version: "2"

services:
    gluetun-update:
        image: qmcgaw/gluetun
        command: update -enduser -providers ivpn
        restart: "no"

    deunhealth:
        image: qmcgaw/deunhealth
        container_name: deunhealth
        network_mode: "none"
        environment:
        - LOG_LEVEL=info
        - HEALTH_SERVER_ADDRESS=127.0.0.1:9999
        - TZ=America/Montreal
        restart: always
        volumes:
        - /var/run/docker.sock:/var/run/docker.sock

    gluetun:
        image: qmcgaw/gluetun
        depends_on:
        - gluetun-update
        labels:
        - deunhealth.restart.on.unhealthy=true
        cap_add:
        - NET_ADMIN
        devices:
        - /dev/net/tun:/dev/net/tun
        environment:
        - VPN_SERVICE_PROVIDER=ivpn
        - OPENVPN_USER=USERNAME
        - OPENVPN_PASSWORD=PASSWORD
        - SERVER_COUNTRIES=Switzerland,Iceland,Finland,Czechia,Romania,Bulgaria,Serbia,Hungary,Slovenia,Australia
        - FIREWALL=on
        - FIREWALL_ALLOW_PORTS=443,54321
        - BLOCK_MALICIOUS=on
        - DOT=on
        - DNS_ADDRESS=9.9.9.9
        - TZ=Europe/Amsterdam
        ports:
        - 443:443
        - 54321:54321

    xui:
        image: alireza7/x-ui
        network_mode: "service:gluetun"
        depends_on:
        - gluetun
        volumes:
        - /home/ops/db:/etc/x-ui/
        - /home/ops/xray_config/config.json:/app/bin/config.json

Здесь мы используем несколько сервисов и каждый нужен в своей мере:
  • gluetun-update - обновляет списки серверов перед запуском одноименного сервиса.
  • deunhealth - перезапускает gluetun, в случае падения по какой-либо причине, без необходимости перезапускать все.
  • xui - соответственно наш VLESS маршрутка до VPS.
Gluetun — единственный маршрут в интернет для Xray. Всё остальное либо сидит в туннеле, либо не имеет сети вообще.
Кстати для удобства можем сделать следующие alias в .bashrc:
Bash:
alias stop='docker-compose down && docker-compose rm -f'
alias start='docker-compose up -d'
alias restart='stop && start'
alias status='docker-compose logs -f'
И обновить терминал:
Bash:
source .bashrc
Дальше нам нужно создать пару папок и файл, потому-что docker почему-то упорно пытается создать директорию вместо файла:
Выполняем через sudo, docker-демон работает от имени root,архитектурная особенность у него такая.
Bash:
sudo mkdir db && sudo mkdir gluetun && mkdir xray_config && touch xray_config/config.json
  • db - будет лежать sqlite.db от x-ui.
  • gluetun - servers.json для vpn провайдеров, создается сам при необходимости..
  • xray_config - json с конфигом соответственно.
поднять сами контейнеры:
Bash:
docker-compose up -d
или:
Bash:
start | restart
5. SSH
SSH — критическая точка. Пароли исключены.
Генерация клиентского ключа:
Bash:
ssh-keygen -f ~/client_key
Bash:
install -m 700 -d /home/ops/.ssh
Bash:
cat ~/client_key.pub >> /home/ops/.ssh/authorized_keys
Bash:
chmod 600 /home/ops/.ssh/authorized_keys
Bash:
chown -R ops:ops /home/ops/.ssh
Настройка сервера SSH:
/etc/ssh/sshd_config
Bash:
nano /etc/ssh/sshd_config
 PasswordAuthentication no
 PubkeyAuthentication yes
 PermitRootLogin prohibit-password
Подключение клиента(Не забудьте скачать приватный ключ):
Bash:
sftp root@ip
get /home/ops/client_key
только потом:
Bash:
systemctl restart sshd
На Host системе:
Bash:
ssh -i ~/client_key ops@vps_ip
Port forwarding к панели x-ui(54321 пример порта):
Bash:
ssh -i ~/client_key -L 54321:127.0.0.1:54321 ops@vps_ip
TLS для x-ui не нужен, канал уже зашифрован через SSH.

6. Xray + x‑ui (VLESS + XHTTP + REALITY)
С помощью
Bash:
docker logs root_xui_1
смотрим на каком порту запустился наш x-ui.
И поскольку мы уже перезашли на сервер с port-forward через ssh, открываем в браузере: http://127.0.0.1:54321- admin:admin
  • Идем в настройки панели , авторизация и меняем пользователя и пароль.
  • 1769403611564.png

  • Там же в соседней вкладке, можем поменять panel path: "/", например: "ZHNhZHNha3Nha2Rsc2phZmtsYXM7amdsJ2thZ2phZGtsO2pka2w7c2FqZGtsYXM7bG"
    1769403573580.png
  • Открываем настройки xray, отключаем логирование полностью.
    1769403530479.png
Теперь у нас панель защищена,с учетом отсутствия доступа извне, и шифрования ssh при port-forward.
Если уж совсем печетесь,то можете сгенерировать certs и подставить их в соответствующие настройки панели.

В данной схеме Xray используется исключительно как входная‑точка.
Его роль — принять клиентский трафик, замаскировать его под легитимный HTTPS и передать дальше, не оставляя артефактов, по которым DPI или пассивный наблюдатель может уверенно классифицировать соединение как прокси или VPN.
  • Связка VLESS + XHTTP + REALITY на 443‑м порту в 2026 году — это фактически минимальный набор, который ещё позволяет жить под агрессивным DPI без выделяющихся сигнатур.​
  • Отсутствие TLS‑сертификата на сервере означает, что нечего отзывать, анализировать или связывать с конкретным IP. Сервер не хранит private key и не участвует в PKI‑цепочке, что резко снижает след на стороне хоста.​
  • REALITY использует реальный TLS‑handshake к существующему домену, а не симуляцию. Для DPI это выглядит как обычное HTTPS‑соединение к популярному ресурсу, без "кривых" параметров ClientHello.​
  • XHTTP убирает артефакты WebSocket: нет Upgrade, нет характерных заголовков, нет долгоживущих соединений с неестественным паттерном. Трафик выглядит как обычные HTTP‑запросы поверх TLS.​

Кратко о настройке Inbound:
Inbound настраивается максимально скучно и реалистично. Любая "креативность" здесь работает против OPSEC.

Важно про IP и x-ui при использовании Gluetun
Из-за включённого FIREWALL=on в Gluetun и режима
Bash:
network_mode: "service:gluetun"
контейнер x-ui полностью наследует сетевое пространство VPN-контейнера и не видит внешний IP хоста.
x-ui в такой схеме:
— не знает публичного IP VPS
— видит только loopback и интерфейсы Gluetun
— не способен корректно автоподставить IP в inbound / client config

При создании inbound - поле IP / Address нужно оставить пустым.
В итоге при генерации конфигураций и QR-кодов в x-ui:

— x-ui будет генерировать конфиг с 127.0.0.1
— это ожидаемое и корректное поведение в данной архитектуре
Как с этим работать
В клиентском приложении или перед отправкой URI:
— вручную заменяем 127.0.0.1 на реальный публичный IP или домен VPS
Все остальные параметры (UUID, REALITY-keys, SNI, transport) остаются без изменений
1769403642298.png




Опции Inbound:
  • Protocol: VLESS
  • Port: 443​
  • Transmission / Transport:XHTTP
    • XHTTP — ключевой момент. Он убирает WebSocket-артефакты (Upgrade, Connection, неестественные long-lived streams), которые давно используются DPI для классификации прокси. Трафик выглядит как серия обычных HTTPS-запросов с реалистичными таймингами.
  • Security:REALITY
    • REALITY не "шифрует TLS", а встраивается в реальный TLS-контур. Сервер не хранит сертификат, не участвует в PKI и не оставляет криптографический след, который можно отозвать, зафиксировать или связать с IP.
  • Authentication:ml-kem-768
    • Используется только если клиент поддерживает. Если клиент не поддерживает:
    • authentication → clear
    • — иначе Get new keys
    • ml-kem-768 не влияет на DPI видимость, он влияет исключительно на криптографическую стойкость сессии при перехвате.
  • Host:st.ozone.ru (пример)
    • Host должен строго совпадать с реальным HTTP-поведением домена. Никаких выдуманных путей или псевдо API, нехарактерных для ресурса.
  • Path:/api (пример)
    • Используется только если:
    • — путь реально существует или выглядит типовым для выбранного домена
    • — нет аномальных ответов
    • — нет нестандартных размеров payload
    • Слишком экзотический path — быстрый триггер эвристик.
  • SNI:st.ozone.ru
    • Обязан быть:
    • — массовым
    • — стабильным
    • — реально используемым CDN / сервисом
    • — с TLS-параметрами, соответствующими реальному ClientHello
  • Dest:st.ozone.ru
    • Dest и SNI должны совпадать.
    • Любое расхождение SNI / Dest / Host — один из самых простых и надёжных DPI-индикаторов прокси-туннеля.
  • uTLS:qq
    • Профиль китайского QQ-браузера долгое время оставался "белым" даже под агрессивным DPI.
    • Он не выглядит как Chrome/Firefox, но при этом массово встречается в реальном трафике, что снижает приоритет анализа.
  • Private / Public keys:
  • Get new cert
    • Ключи REALITY должны быть уникальны для каждого inbound и тем более сервера.
    • Повторное использование ключей между серверами создаёт коррелируемый криптографический отпечаток.
  • MLDSA-65 (seed / verify):
  • Опционально. Используется для пост-квантовой подписи, если клиент поддерживает.
    • Как и ml-kem:
    • — не влияет на DPI
    • — не маскирует трафик
    • — повышает стойкость при долгосрочном перехвате
    • Если клиент не поддерживает — отключается без потери OPSEC.
  • Sniffing:off
    • Sniffing в нашем сценарии:
    • — не нужен
    • — увеличивает объём логики
    • — повышает риск артефактов
    • — не даёт выигрыша в маскировке
    • Sniffing оправдан только в сложных routing-сценариях, не в минималистичном транзите.
1769403670632.png



Отдельно и принципиально:
один IP — один inbound — один протокол.
Несколько inboundов, fallbackов, резервных портов и на всякий случай:
  • — формируют профиль сервера
  • — увеличивают корреляцию
  • — дают DPI больше статистики
  • — упрощают активное зондирование
1769405495635.png







1769403440444.png


Ресурсы:
Gluetun - WIKI
https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers
 

Вложения

  • 1769403245857.png
    1769403245857.png
    44.9 КБ · Просмотры: 32
Последнее редактирование:
Хороший и быстрый мануальчик, спасибо автору)

Только от себя добавил бы, лучше все же держать морду на самой машине, выставив в графу Listen IP локальню 127.0.0.1: так доступ к панельке будет получаться по двухфакторной аутентификации - нужно и ключик SSH предъявить(пробросив порт на свою тачку через дефолтные возможности SSH), и сам L:P прописать).

Да и в root login лучше сразу и откровенно в правилах на тачке вкинуть no: хотят рут на дедике? Пусть берут и ваш ключ, и ваш пароль для суда)
Просто добавить своего нового пользака под юзермод суды, и все ;)
 
Мне нравится идея создания собственной VPN-инфраструктуры с помощью Gluetun, но мне не нравится процесс маршрутизации всего трафика сначала через один VPS.а

Я надеялся на приложение, где вы выбираете сервер и нажимаете «Подключиться», и вы подключаетесь к одному из серверов Gluetun. VPS просто получал бы список серверов, поэтому вам не пришлось бы вводить этот список для каждого клиента.
 
Последнее редактирование:
Хороший и быстрый мануальчик, спасибо автору)

Только от себя добавил бы, лучше все же держать морду на самой машине, выставив в графу Listen IP локальню 127.0.0.1: так доступ к панельке будет получаться по двухфакторной аутентификации - нужно и ключик SSH предъявить(пробросив порт на свою тачку через дефолтные возможности SSH), и сам L:P прописать).

Да и в root login лучше сразу и откровенно в правилах на тачке вкинуть no: хотят рут на дедике? Пусть берут и ваш ключ, и ваш пароль для суда)
Просто добавить своего нового пользака под юзермод суды, и все ;)
Да вы правы! Если честно изначально хотел все это прописать, хотя бы информативно, но так устал, что ушел от начального плана :)
В следующий раз постараюсь более детально писать,и внимательнее отнестись к содержимому.
 
Мне нравится идея создания собственной VPN-инфраструктуры с помощью Gluetun, но мне не нравится процесс маршрутизации всего трафика сначала через один VPS.а

Я надеялся на приложение, где вы выбираете сервер и нажимаете «Подключиться», и вы подключаетесь к одному из серверов Gluetun. VPS просто получал бы список серверов, поэтому вам не пришлось бы вводить этот список для каждого клиента.
мб я не совсем правильно понял, но напишу как сам это вижу
идея с gluetun норм, но проблема не в нем, а в ожиданиях. хочется не один vps через который идет весь трафик, а что то ближе к обычному vpn клиенту где выбираешь страну и подключаешься. vps при этом нужен скорее как точка контроля и управления а не обязательный транзит
один два vps нужны чтобы размазать юрисдикцию вывести трафик из страны клиента и не светить клиента напрямую перед vpn провайдером. это не vpn в лоб а инфраструктурный слой
gluetun удобен тем что уже умеет работать с разными провайдерами и кастомным openvpn или wireguard. если нужны разные страны просто поднимаются несколько docker контейнеров с gluetun под разные регионы
дальше есть два пути. либо играться с fwmark где xray помечает трафик а ядро linux раскидывает его по разным маршрутам и получается несколько outbound как в коммерческих сервисах. либо совсем просто сделать web морду или бота который по выбору страны правит docker compose и перезапускает нужный gluetun
в итоге gluetun это кирпичик а не готовое решение. дальше вопрос либо ковыряться в routing либо автоматизировать смену exit ноды
 
мб я не совсем правильно понял, но напишу как сам это вижу
идея с gluetun норм, но проблема не в нем, а в ожиданиях. хочется не один vps через который идет весь трафик, а что то ближе к обычному vpn клиенту где выбираешь страну и подключаешься. vps при этом нужен скорее как точка контроля и управления а не обязательный транзит
один два vps нужны чтобы размазать юрисдикцию вывести трафик из страны клиента и не светить клиента напрямую перед vpn провайдером. это не vpn в лоб а инфраструктурный слой
gluetun удобен тем что уже умеет работать с разными провайдерами и кастомным openvpn или wireguard. если нужны разные страны просто поднимаются несколько docker контейнеров с gluetun под разные регионы
дальше есть два пути. либо играться с fwmark где xray помечает трафик а ядро linux раскидывает его по разным маршрутам и получается несколько outbound как в коммерческих сервисах. либо совсем просто сделать web морду или бота который по выбору страны правит docker compose и перезапускает нужный gluetun
в итоге gluetun это кирпичик а не готовое решение. дальше вопрос либо ковыряться в routing либо автоматизировать смену exit ноды
У цепочек с такими решениями, как gluetun(раньше с пробросом портов на муллваде это делалось и без него, кто знает, тот знает) есть несомненное преимущество: сервера там дружелюбны к Tor, и подключение к сети Tor вполне себе происходит без поиска "того самого" провайдера, что пускает без бриджЕй.
А бриджи в последнее время, если новости почитать, - штука уж очень интересная, и все не в хорошую сторону).


Нет ничего плохого в том, чтобы создать на основе gluetun своеобразный "мост" между запрещенным условным муллвадом и собой, это лишь добавит надежности. Кирпичик али нет, но прятаться за двумя юрисдикциями(особенно если обе фирмы позиционируют себя как приватные решения) - надежнее, чем за одним, в любом случае.
 
Тем, кто хочет полноправный мультихоп, ничто не мешает ознакомиться с видеомануалом Зираля(первое, что пришло на ум), только еще и впс хорошо защитить: тот же 3X-UI в формате вебморды очень удобен и идеально подойдет для таких целей, мультихоп соберете словно лего, не выходя из GUI.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх