В этой статье описаны различные актуальные на 2026 год способы обхода блокировок - начиная от простых сетапов и постепенно дополняя до более сложных. Также будут описаны актуальные на февраль 2026-го алгоритмы блокировок, применяемые цензорами.
Подразумевается, что у вас уже арендован и настроен VPS (или несколько). Для настройки VPS наберите следующий текст в свою любимую LLM. Рекомендую lmarena (gemini/gpt/grok/claude)
Текст для LLM
Debian 12 Дай пошаговую инструкцию по минимальной настройке VPS - создание пользователя, защиту SSH, конфигурацию ufw итд1/5 Direct · 2/5 Cascade · 3/5 WARP · 4/5 Split-Tunneling · 5/5 CDN
Базовый сетап. Клиент подключается напрямую к зарубежному VPS. При активных мобильных БС не будет работать. На проводном подключении уже подвергается шейпингу по описанному здесь сценарию. По сути применяется грубая эвристика - IP-адрес из зарубежной подсети + размер данных переданных в одном TCP-соединении превышает 15кб -> режем трафик. При этом некоторые зарубежные подсети из критической инфраструктуры в белом списке. Это довольно эффективно - цензору не нужно заморачиваться с обнаружением протоколов.
Установим xray с помощью скрипта-установщика
Скрипт скачает и установит бинарник xray-core (/usr/local/bin/xray), создаст директорию с конфигом (/usr/local/etc/xray), директорию для geoip.dat файлов (/usr/local/share/xray), создаст systemd сервис для автоматического запуска
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ installСгенерируем ключи для авторизации клиента и шифрования
# uuid для авторизации vless клиента
xray uuid
# tls ключи, privateKey - сервер, password - клиент
xray x25519Возьмем шаблон конфига из репозитория с примерами конфигов (комментарии нужно убрать)
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443, // порт 443 для маскировки
"protocol": "vless",
"settings": {
"clients": [
{
"id": "", // id для авторизации клиента, полученное с xray uuid
"flow": ""
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "/yourpath" // укажите свой секретный путь, например /torvalds/linux
},
"security": "reality",
"realitySettings": {
"target": "example.com:443", // сайт, под который "маскируется" reality во время tls хендшейка, например github.com:443
"serverNames": [
// список доступных SNI target
// используйте команду xray tls ping github.com, чтобы получить допустимый список для хоста
// пример: "github.com", "www.github.com"
],
"privateKey": "", // "private key" полученный с xray x25519
"shortIds": [ // используется для идентификации конкретного клиента
// "", // с этим значением на клиенте можно не заполнять shordId
"00" // длина должна быть кратна 2, от 2 до 16 символов, символы 0-9, a-f
]
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
]
}Если вдруг поехало форматирование json файла
jq . config.json > fixed.json && mv fixed.json config.jsonОтредактируем файл конфига
cd /usr/local/etc/xray # путь по умолчанию, если использовали скрипт-установщик
sudo vim config.json # :wq - записать и выйтиПроверим конфиг и перезапустим xray
xray -test -c /usr/local/etc/xray/config.json
sudo systemctl restart xray
sudo systemctl status xray # должно быть без ошибокВ голом xray-core нет возможности генерации ссылки для подключения, но это можно сделать вручную, подставив нужные значения
vless://uuid@ip:port?encryption=none&host=github.com&fp=chrome&mode=auto&path=/torvalds/linux&pbk=password&security=reality&sid=shortid&sni=github.com&type=xhttpВ качесте десктопного графического клиента можно использовать, например, Throne
Во всех клиентах под капотом используют либо тот же бинарник xray-core, либо sing-box (код которого лучше оптимизирован). Для использования достаточно импортировать ссылку подключения.
Мобильные клиенты - V2RayNG, Happ, Hiddify.
3X-UI - графическая панель, тулинг для xray-core, позволяет создавать конфиги в UI, генерировать ссылки и QR-коды подключения для клиентов, а также добавляет функционал подписок - ссылок, с помощью которых клиент всегда может получить актуальную версию конфигов.
Если вы устанавливали 3X-UI до этого
В старых версиях установочного скрипта 3X-UI было сразу несколько способов выстрелить себе в ногу. Панель шла с кредами admin/admin, по http, сервис подписок включен по умолачанию по известному пути /sub/. Есть неплохая вероятность, что ваша панель скомпроментированаИспользуем установычный скрипт с официального репозитория 3x-ui
# перед запуском скрипта поменяем пользователя на root
sudo -i
# установим acme для управления сертификатами
apt update && apt upgrade -y
apt install -y curl wget socat cron
curl https://get.acme.sh | sh -s email=myvpnxxx@gmail.com # email может быть любым, но домен должен быть реальным
/root/.acme.sh/acme.sh --set-default-ca --server letsencrypt
# нужно открыть порт 80 для установки сертификата
ufw allow 80/tcp
# установочный скрипт
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)Установочный скрипт предложит вам задать порт и установить сертификат LetsEncrypt. Установка сертификата в новых версиях 3x-ui теперь обязательна - домен покупать для этого не нужно, достаточно выбрать опцию Let's Encrypt for IP Address (6-day validity, auto-renews)
В новых версиях, по умолчанию будут сгенерированы случайные имя пользователя, пароль, порт и секретный путь. На выходе увидим:
═══════════════════════════════════════════
Panel Installation Complete!
═══════════════════════════════════════════
Username: user
Password: pass
Port: port
WebBasePath: secret_path
Access URL: https://ip:port/secret_path
# сохраните выдачу и не забудьте открыть порт, выбранный для панели
ufw allow xxx/tcpПосле входа в панель, следует сменить путь для подписок с дефолтного /sub/ на что-то более секретное в Panel Settings -> Subscription. Также смените listen port и не забудьте его открыть. Либо если вам не нужен функционал подписок, отключите subscription service.
Для дополнительной безопасности можно включить двухфакторную аутентификацию в Panel Settings -> Authentication -> Two-factor authentication.
Теперь накликаем такой же инбаунд, как в конфиге сырого xray-core выше

- Listen IP - 0.0.0.0 - хотим слушать на всех интерфейсах
- Port - рекомендован 443 для маскировки
- Protocol - протокол аутентификации, vless - самый актуальный
- Transmission - транспорт (как передаем данные: tcp/websocket/grpc/xhttp)
- Path - путь, по которому делаем запрос grpc/xhttp/websocket
- Security - протокол шифрования
- uTLS - под какой браузер маскируемся
- Target - с какого домена reality берет сертификат и прочие данные для tls хендшейка
- SNI - список допустимых доменов для target - xray tls ping github.com (используется в client hello и для получения конкретного сертификата от target)
- Short IDs - используется для идентификации клиентов
- Public Key - публичный ключ tls - xray x25519 "password"
- Private Key - приватнывй ключ tls - xray x25519 "private key"
После добавления инбаунда, можем нажать на плюсик рядом с ним - тут можно получить QR-код для подключения клиентов, либо информацию: ссылку на конфиг, ссылку на подписку

В клиент можно импортировать как ссылку конфига (URL), так и ссылку подписки (Subscription URL). В случае с подпиской при изменении настроек инбаунда либо добавлении/удалении инбаундов для этого клиента, клиент может автоматически обновить конфиги по ссылке подписки, в случае с ссылкой на конфиг - ему придется отправить новую ссылку.
Также по ссылке подписки можно переходить - по ней будет лендинг для конкретного uuid клиента c QR-кодом, ссылками на приложения-клиенты на Android и iOS, список всех конфигов-ссылок для клиента, а также данные по трафику.
Remnawave - инструмент для оркестрации множества VPS (control plane) с xray-core (Remnawave node). Remnawave позволяет поднимать сколько угодно серверов с Remnawave node и управлять всеми с единого сервера-панели.
У инструмента много различных фич и возможностей, дополнительно можно ознакомиться по ссылке.
В каскадном сетапе клиент стучится на VPS в РФ, этот VPS роутит трафик на зарубежный VPS.
- Если VPS в РФ имеет IP-адрес из белого списка - мы получаем обход как белых списков мобильного интернета, так и обход шейпинга трафика от ТСПУ на проводном интернете
- Если VPS в РФ имеет IP-адрес не из белого списка - мы обычно получаем обход шейпинга трафика от ТСПУ на проводном интернете. Почему? Видимо, алгоритмы шейпинга развернуты еще не на всех коробках ТСПУ либо намеренно развернуты не в полную силу на ISP хостингов и дц
Проверить, использует ли ваш ISP шейпинг трафика к зарубежным подсетям можно тут Для убедительности прикладываю два скрина
- C обычного VPS в РФ (не из БС) - доступны практически все IP
- С моего проводного ISP (SkyNet) - заблокированы практически все IP
На практике это выглядит так, что больше половины зарубежных сайтов не открываются. Почти чебурнет, но все еще есть к чему стремиться.
Как работают белые списки
- CIDR - блокировка доступа к IP-адресам не из указанного диапозона (123.123.123.123/24)
- SNI - блокировка подключений с SNI отличным от белого списка (ya.ru, vk.com)
Как можно обойти?
- В диапозоны разрешенных IP-адресов попадают адреса критической инфраструктуры (yandex, vk, ozon, wb и т.д.)
- В списки разрешенных SNI попадают соответствующие им домены
- Идея - выловить IP-адрес из белого списка у крупных поставщиков IaaS (yandex cloud, vk cloud), использовать соответствующий поставщику "белого" IP-адреса SNI в Reality
Ограничения
- Поставщики IaaS начали активно блокировать пользователей за перебор IP-адресов и за использование их сервисов для обхода блокировок
- Операторы сотовой связи улучшают белые списки, убирая лишние диапозоны БС
- В конечном итоге обход БС будет невозможен, поэтому их обход скорее временное явление
Я не буду описывать процесс подбора IP-адреса из белого списка - в остальном, настройка VPS с IP-адресом из БС аналогична настройке VPS в РФ с обычном IP-адресом.
Промежуточный VPS будет перехватывать пакеты от клиента и перенаправлять их на зарубежный VPS - для этого нам нужен только iptables. Преимущество решения в максимальной простоте и минимальном использовании ресурсов.
#!/bin/bash
VPS2_IP="123.123.123.123" # IP зарубежного VPS
TARGET_PORT="443" # порт инбаунда vless на зарубежном сервере
LOCAL_PORT="443" # локальный порт
SSH_PORT="22" # порт SSH
# Включим перенаправление пакетов (IP Forwarding)
sudo sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-ipforward.conf
sudo sysctl -p /etc/sysctl.d/99-ipforward.conf
sudo apt update
sudo apt install iptables iptables-persistent
# Сбросим все правила к дефолтным (ACCEPT), чтобы не потерять доступ в процессе
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -F
sudo iptables -t nat -F
sudo iptables -X
sudo iptables -t nat -X
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport "$SSH_PORT" -j ACCEPT
# Все что пришло на LOCAL_PORT, отправляем на VPS2:TARGET_PORT
# TCP
sudo iptables -t nat -A PREROUTING -p tcp --dport "$LOCAL_PORT" -j DNAT --to-destination "$VPS2_IP:$TARGET_PORT"
# UDP (для QUIC)
sudo iptables -t nat -A PREROUTING -p udp --dport "$LOCAL_PORT" -j DNAT --to-destination "$VPS2_IP:$TARGET_PORT"
# Подменяем IP клиента на IP этого VPS
# Это нужно, чтобы VPS2 отвечал этому VPS, а не пытался ответить клиенту напрямую
sudo iptables -t nat -A POSTROUTING -p tcp -d "$VPS2_IP" --dport "$TARGET_PORT" -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p udp -d "$VPS2_IP" --dport "$TARGET_PORT" -j MASQUERADE
# Разрешаем прохождение переадресованных пакетов
sudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A FORWARD -p tcp -d "$VPS2_IP" --dport "$TARGET_PORT" -j ACCEPT
sudo iptables -A FORWARD -p udp -d "$VPS2_IP" --dport "$TARGET_PORT" -j ACCEPT
# Блокируем все остальное
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo netfilter-persistent save
sudo netfilter-persistent reloadКонфиг xray остается неизменным за исключением IP-адреса и порта - нужно указать адрес промежуточного VPS и порт, с которого переадресуем пакеты.
HAProxy - reverse proxy и балансировщик нагрузки. Удобно, если у вас несколько зарубежных VPS и вы хотите максимального аптайма.
Установка и настройка
sudo apt update
sudo apt install haproxy -y
sudo vim /etc/haproxy/haproxy.cfg
# Секцию global оставить как есть
defaults
log global
mode tcp # L4 проксирование
option tcplog
option dontlognull
timeout connect 5000 # таймаут на установку соединения с бекенд хостами, мс
timeout client 50000 # таймаут ожидания следующих данных от клиента
timeout server 50000 # таймаут ожидания данных от сервера
# слушаем 443 и перенаправляем на указанный бекенд
frontend vless-in
bind *:443
default_backend vless-single-node
# бекенд с одним хостом, только для переадресации
backend vless-single-node
mode tcp
# healthcheck
# inter 10s - проверять каждые 10 секунд
# fall 3 - если 3 раза подряд ошибка, считаем сервер мертвым
# rise 2 - если 2 раза подряд ок, считаем живым
# HAProxy не будет пытаться стучаться в мертвый сервер в отличии от iptables
server vps_eu 123.123.123.123:443 check inter 10s fall 3 rise 2
# бекенд с множеством хостов, балансировка
backend vless-nodes
mode tcp
# Доступные алгоритмы балансировки
# leastconn - выбирает хост с меньшим количеством установленных соединений
# roundrobin - по очереди
# source - привязывает IP клиента к серверу (клиент с одним IP всегда попадает на один и тот же сервер)
# random - случайный выбор
balance leastconn
server vps_eu1 111.111.111.111:443 check inter 5s fall 3 rise 2
server vps_eu2 112.112.112.112:443 check inter 5s fall 3 rise 2
server vps_eu3 113.113.113.113:443 check inter 5s fall 3 rise 2
# статистика (необязательно)
listen stats
bind :8404
mode http
stats enable
stats uri /statsПроверим конфиг и перезапустим
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxyВ предыдущих вариантах использовали "тупые" L4-прокси, которые знают только при IP:port. Опишу сценарий использования xray-core в качестве промежуточного VPS.
Промежуточный VPS у нас в ДЦ РФ - зачастую они не шейпят трафик до того же youtube, к тому же на ютубе нет рекламы для ру сегмента, поэтому на промежуточном VPS будем
- Принимать трафик на inbound vless xhttp reality (tls termination)
- Если домен geosite:youtube или geosite:ru -> отправлять на direct аутбаунд (т.е. пойдем на ютуб с VPS РФ)
- Все остальное -> отправляем на аутбаунд зарубежного VPS (tag proxy)
Конфиг будет выглядеть так
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443, // порт 443 для маскировки
"protocol": "vless",
"settings": {
"clients": [
{
"id": "", // id для авторизации клиента, полученное с xray uuid
"flow": ""
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "/yourpath" // укажите свой секретный путь, например /torvalds/linux
},
"security": "reality",
"realitySettings": {
"target": "example.com:443", // сайт, под который "маскируется" reality во время tls хендшейка, например github.com:443
"serverNames": [
// список доступных SNI target
// используйте команду xray tls ping github.com, чтобы получить допустимый список для хоста
// пример: "github.com", "www.github.com"
],
"privateKey": "", // "private key" полученный с xray x25519
"shortIds": [ // используется для идентификации конкретного клиента
// "", // с этим значением на клиенте можно не заполнять shordId
"00" // длина должна быть кратна 2, от 2 до 16 символов, символы 0-9, a-f
]
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
}
],
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "123.123.123.123", // IP зарубежного VPS
"port": 443, // порт
"users": [
{
"id": "uuid", // uuid для аутентификации vless
"encryption": "none",
"flow": ""
}
]
}
]
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "/torvalds/linux" // путь xhttp
},
"security": "reality",
"realitySettings": {
"serverName": "github.com",
"publicKey": "password", // password с xray x25519
"shortId": "00", // short id клиента
"fingerprint": "chrome"
}
},
"tag": "proxy"
},
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["geosite:youtube, geosite:ru"],
"outboundTag": "direct"
},
{
"type": "field",
"outboundTag": "proxy"
}
]
}
}В 3x-ui
- Добавьте vless xhttp reality инбаунд
- Добавьте аутбаунд зарубежного VPS: Xray configs -> Outbounds -> Add outbound -> JSON -> Link (tag proxy)
- Обновите Routings rules: Domain: geosite:youtube. geosite:ru, Outbound Tag: proxy
Подробнее про маршрутизацию в xray можно почитать в доке
Минусы данного подхода в высоких накладных расходах на tls termination, дополнительное шифрование и дешифрование данных (client ⇄ VPS1 ⇄ VPS2 ⇄ HTTPS).
WARP - сервис от cloudflare, что-то вроде VPN, но не совсем. Допустим, вы находитесь в Германии и делаете запрос к сайту, что находится в США.
- WARP направляет трафик в ближайшую ноду Cloudflare в Германии
- Оттуда трафик летит по приватной сети Cloudflare и выходит в оптимальной ноде
- С ноды CF в США идет запрос на сайт
- Возвращается ответ по такому же пути
Отсюда название WARP - пакеты как бы телепортируются через частную сеть CF, минуя стандартный путь через сеть сетей - роутеров национальных и глобальных ISP.
Но использовать WARP мы будем по другой причине. Регион IP-адреса арендованного зарубежного VPS может в некоторых сервисах (например, google) определяться как Китай или Иран. В таком случае у нас не будет доступа, например, к google gemini. WARP поможет нам "отбелить" такой IP-адрес. При этом, мы не хотим весь трафик маршрутизировать через WARP, для этого используем routing правила xray-core на зарубежном VPS.
Проверить регионы вашего VPS можно через скрипт:
# перед запуском рекомендую прочитать скрипт (либо вбить в LLM) и форкнуть себе
bash <(wget -qO- https://raw.githubusercontent.com/vernette/ipregion/refs/heads/master/ipregion.sh)Установка и настройка WARP на зарубежном VPS
# add warp gpg key
curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg
# add warp repo
echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflare-client.list
# install
apt update
apt install cloudflare-warp
# register warp
echo "y" | warp-cli registration new
# mode socks proxy
WARP_PORT=6000
warp-cli mode proxy
warp-cli proxy port $WARP_PORT
# connect
warp-cli connectДобавим routing правила в конфиг (чистый xray-core)
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443, // можно заменить на 22/23 - иногда помогает обойти шейпинг
"protocol": "vless",
"settings": {
"clients": [
{
"id": "", // id для авторизации клиента, полученное с xray uuid
"flow": ""
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "/yourpath" // укажите свой секретный путь, например /torvalds/linux
},
"security": "reality",
"realitySettings": {
"target": "example.com:443", // сайт, под который "маскируется" reality во время tls хендшейка, например github.com:443
"serverNames": [
// список доступных SNI target
// используйте команду xray tls ping github.com, чтобы получить допустимый список для хоста
// пример: "github.com", "www.github.com"
],
"privateKey": "", // "private key" полученный с xray x25519
"shortIds": [ // используется для идентификации конкретного клиента
// "", // с этим значением на клиенте можно не заполнять shordId
"00" // длина должна быть кратна 2, от 2 до 16 символов, символы 0-9, a-f
]
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
},
{
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": $WARP_PORT
}
]
},
"tag": "warp"
}
],
"routing": {
"rules": [
{
"outboundTag": "warp",
"domain": [
"geosite:google-deepmind"
]
}
]
}
}Также WARP можно настроить для панели 3x-ui:
- Создадим WARP outbound - Xray configs -> Outbounds -> WARP -> Create -> Add
- Routing rules -> Add rule -> (Domain: geosite:google, gemini.google.com, Outbound tag: warp)
- Включите sniffing в настройках инбаунда, чтобы работал роутинг по доменам
Маршрутизация в xray-core похожа на L3 Data Plane
- Пакет поступает на один из входящих интерфейсов (секция inbounds), и в зависимости от правил маршрутизации (секция routing), отправляется в соответствующий исходящий интерфейс (секция outbounds)
- Для идентификации конкретного inbound/outbound используются теги
- На скрине читается: если домен из списка доменов google, отправить пакет на outbound с тегом "warp"
Подробнее про маршрутизацию - в официальной документации
Настройка туннелирования на роутере обладает рядом преимуществ:
- Трафик туннелируется для всех клиентов роутера, нет необходимости ставить клиентов на каждый девайс
- Можно использовать на рабочих ноутбуках
Процесс прошивки конкретной модели роутеров описан на официальном сайте OpenWRT. Для целей раздельного туннелирования подойдут не все роутеры - рекомендуется как можно более "жирный" роутер (процессор, память, диск) с простым процессом прошивки и поддержкой, например GL.iNet GL-MT6000
Существует готовое решение для настройки split tunnel на роутерах OpenWRT - podkop
Далее будет описан способ ручной настройки через nftables, ip route/rule и dnsmasq + dnscrypt-proxy.
Скачаем и распакуем файлы
opkg update
opkg install unzip
cd /tmp
# замените на актуальную версию и архитектуру процессора
# uname -m
# aarch64 arm64-v8a
# armv7l arm32-v7a
# mipsel mipsle
wget https://github.com/XTLS/Xray-core/releases/download/v26.1.23/Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
mkdir -p /etc/xray
mv geoip.dat geosite.dat /etc/xray/
mv xray /usr/bin/xray
chmod +x /usr/bin/xrayНастроим сервис
vim /etc/init.d/xray
# вставьте содержимое#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1
PROG=/usr/bin/xray
CONFIG=/etc/xray/config.json
start_service() {
procd_open_instance
procd_set_param command $PROG run -c $CONFIG
procd_set_param env XRAY_LOCATION_ASSET=/etc/xray
procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param file $CONFIG
procd_close_instance
}
stop_service() {
killall xray 2>/dev/null
}
reload_service() {
stop
start
}Конфиг клиента vless xhttp (комментарии нужно убрать)
vim /etc/xray/config.json
{
"inbounds": [
{
"tag": "tproxy-in", // transparent proxy inbound
"protocol": "dokodemo-door",
"listen": "::",
"port": 4444,
"settings": {
"network": "tcp,udp",
"followRedirect": true
},
"streamSettings": {
"sockopt": {
"tproxy": "tproxy",
"tcpFastOpen": true
}
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls", "quic"]
}
},
{
"tag": "test-socks", // socks inbound для тестирования
"listen": "0.0.0.0",
"port": 1080,
"protocol": "socks",
"settings": {
"udp": true
}
}
],
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "123.123.123.123", // IP VPS в РФ
"port": 443, // порт, который "слушаем" на VPS в РФ
"users": [
{
"id": "uuid", // uuid для аутентификации vless
"encryption": "none",
"flow": ""
}
]
}
]
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "/torvalds/linux" // путь xhttp
},
"security": "reality",
"realitySettings": {
"serverName": "github.com",
"publicKey": "password", // password с xray x25519
"shortId": "00", // short id клиента
"fingerprint": "chrome"
}
},
"tag": "proxy"
},
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
]
}Проверим конфиг, включим и запустим сервис
/usr/bin/xray -test -c /etc/xray/config.json
chmod +x /etc/init.d/xray
/etc/init.d/xray enable
/etc/init.d/xray startПроверим работоспособность конфига + сервера
curl https://ifconfig.me # увидим свой IP
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me # должны увидеть IP зарубежного VPSПосле можно удалить ненужный socks инбаунд.
В конфиге используется transparent proxy inbound (tproxy), который существенно быстрее TUN-интерфейса: трафик перехватывается на уровне ядра и направляется напрямую в сокет xray, без дополнительного прохождения через виртуальный сетевой интерфейс tun.
Все пакеты с меткой 0x1 будем отправлять в таблицу маршрутизации tproxy
Все пакеты попавшие в таблицу tproxy переадресуем себе же на loopback
vim /etc/iproute2/rt_tables
# добавим в конец
100 tproxy
vim /etc/config/network
# эквивалентно ip rule add fwmark 0x1 lookup tproxy priority 100
# сохраняется между перезагрузками
config rule
option mark '0x1'
option priority '100'
option lookup 'tproxy'
# эквивалентно ip route add local 0.0.0.0/0 dev lo table tproxy
config route
option interface 'loopback'
option table 'tproxy'
option target '0.0.0.0/0'
option type 'local'
service network restart
ip rule show # -> 100: from all fwmark 0x1 lookup tproxy
ip route show table tproxy # -> local default dev lo scope hostСоздадим ipset с IP-адресами подсетей, которые хотим пускать в туннель. Все пакеты с destIP из этого сета, будем помечать меткой 0x1, а также прозрачно перенаправлять на порт 4444. Сам пакет при этом не модифицируется
cd /etc/nftables.d/
vim 30-tproxy.nft
# содержимое
# переменная со списком заранее заданных подсетей отсюда
# https://github.com/itdoginfo/allow-domains
# например, возьмем подсети Discord - subnets / ipv4 / discord.lst
define vpn-subnets = {
138.128.136.0/21, 162.158.0.0/15, 172.64.0.0/13,
34.0.0.0/15, 34.2.0.0/15, 35.192.0.0/12,
35.208.0.0/12, 5.200.14.128/25, 66.22.192.0/18
}
# объявим ipset и заполним его значениями с переменной
set vpn-ipset {
type ipv4_addr
flags interval, timeout
auto-merge
elements = $vpn-subnets
}
# метчим все tcp/udp пакеты с интерфейса "br-lan" (заменить на название своего) и с destIP из IP сета
chain tproxy_prerouting {
type filter hook prerouting priority mangle; policy accept;
iifname "br-lan" ip daddr @vpn-ipset meta l4proto { tcp, udp } tproxy ip to :4444 meta mark set 0x1 accept
}
# также можно метчить все пакеты с локального интерфейса (кроме тех, что предназначаются локально самому роутеру)
#chain tproxy_prerouting {
# type filter hook prerouting priority mangle; policy accept;
# iifname "br-lan" fib daddr type local accept
# iifname "br-lan" meta l4proto { tcp, udp } tproxy ip to :4444 meta mark set 0x1 accept
#}
service firewall restart
nft list sets table inet fw4 # показать сеты таблицы
nft flush set inet fw4 vpn-ipset # сбросить сет
nft add element inet fw4 vpn-ipset { x, y } # добавить элементыИмеем на данный момент:
- Все пакеты с меткой
0x1будут отправлены на сокет xray-core - Все пакеты из IP сета vpn-ipset будут помечены меткой
0x1 - Можем помечать все пакеты вне зависимости от адреса назначения (для дебага)
Остался последний штрих - хотим указать список доменов, чтобы шли через туннель.
Для этого используем dnsmasq (установлен по умолчанию) - dhcp-сервер и dns форвардер/кэшер. В конфиге dnsmasq нужно указать название IP сета, куда будут складываться IP-адреса зарезолвленные с указанного списка доменов.
Сделать это можно через панель Luci - перейдя в Network -> DHCP & DNS -> IP Sets -> Add
- Name of the set - название IP сета созданного в nftables (vpn-ipset)
- FQDN - домен второго уровня, по одному. При указании, например, google.com, будет резолвить также домены следующих уровней: maps.google.com, drive.google.com
Либо через конфиг
vim /etc/config/dhcp
config ipset
option table 'fw4'
option table_family 'inet'
list name 'vpn-ipset'
list domain 'chatgpt.com'
service dnsmasq restartПанель в данном случае использовать удобнее - если наткнулись на заблокированный ресурс, можно зайти в панель и быстро добавить домен в список.
По умолчанию dnsmasq использует plaintext DNS-резолвер от DHCP-сервера оператора связи, т.е. оператор видит и может блокировать DNS-запросы к определенным доменам. Используем прокси-форвардер dnscrypt-proxy2 для шифрования DNS-запросов
opkg update && opkg install dnscrypt-proxy2
vim /etc/dnscrypt-proxy2/dnscrypt-proxy.toml
# пример настройки
server_names = ['google', 'cloudflare', 'scaleway-fr']
listen_addresses = ['127.0.0.53:53']
dnscrypt_servers = false
doh_servers = true
odoh_servers = false
require_nolog = true
# проверка
service dnscrypt-proxy restart
nslookup github.com 127.0.0.53:53Настроим dnsmasq на использование dnscrypt-proxy2
config dnsmasq
...
option noresolv '1' # игнорировать днс-резолвер от ISP
option cachesize '0' # кэширование включено в dnscrypt-proxy
option localuse '1'
list server '127.0.0.53#53' # listen_address в dnscrypt-proxy
service dnsmasq restart
Какую проблему решает CDN (Content delivery network)? Представьте, что вы находитесь в США и делаете запрос к сайту в Германии:
- Байты запросы разделяются на пакеты
- Пакет идет по системе роутеров, объединенных с другими роутерами (сеть сетей), используя алгоритмы OSPF, BGP
- Региональные сети объединены в национальные, а те в свою очередь в глобальные (Tier1) - сети платят за доставку пакетов "выше"
- Таким образом пакет пройдет через немалое количество сетей, и в итоге по глобальной сети между континентами
Роутить пакеты таким образом медленно и дорого, CDN решают эту проблему собственной частной сетью, которой не нужно платить за трафик глобальным ISP, а также кэшированием данных в различных ДЦ по всему миру и предоставлением услуги облачного reverse proxy. Тот же запрос будет выглядеть с CDN примерно так:
- DNS CDN отдает IP-адрес ближайшего к пользователю хоста (региональная сеть, IXP)
- Если ответ на запрос уже закэширован, сразу отдаем пользователю, не выходя из региональной сети
- Если ответа нет в кэше, CDN заберет их с оригинального сервера по своей частной сети, после обновит кэш
- В итоге: минимальная задержка (round-trip time), минимальная нагрузка на оригинальный сервер, дешевый трафик, DDoS-защита
Идея с использованием CDN схожа с идеей подбора "белых" IP для обхода БС мобильных операторов связи. Есть какая-то критическая инфраструктура интернета, которую блокировать не хочется или по крайней мере хочется не всегда - тогда цензор ее пропускает.
Для начала нужно ответить на вопрос, что нам дает этот метод? Учитывая, что проводные ISP уже применяют шейпинг трафика к большинству CDN, эффективно блокируя к ним доступ. Дает еще одно возможное "окно", возможный способ обхода ограничений.
В качестве CDN будем использовать бесплатный аккаунт Cloudflare. У данного метода есть существенное ограничение - его нельзя использовать с Reality, т.к. мы идем через сервера CF - серверу нужен настоящий SNI в ClientHello, чтобы идентифицировать конкретный домен. Если не считать domain fronting, который прикрыли на большинстве CDN, - для роутинга на конкретный домен используется не SNI, а http host header.
Для минимального работающего сетапа, вам понадобится домен и VPS с IP-адресом. При регистрации в CF, у вас будет инструкция по настройке DNS, поэтому описывать я это не буду. На этом этапе у вас уже должен быть домен, зареганный в CF с включенным оранжевым облаком в DNS -> Records.
SSL/TLS режимы
- Off - шифрование отключено. Клиент стучится в http CF домен, CF стучится в http сервер с plaintext
- Flexible - шифрование только client <-> CF, на VPS необязательно, CF стучится в http сервер с plaintext, на VPS сертификат не нужен, в инбаундах xray-core можно использовать security: "none"
- Full - полное шифрование client <-> CF <-> VPS. В данном случае необходимы ключи tls на VPS
- Full (Strict) - то же самое, только нужен сертификат (authority, что подтвердит принадлежность домена именно этому IP)
WebSockets/gRPC
- Websocket - включен по умолчанию, нужен для ws транспорта
- gRPC - нужен для транспортов gRPC и xhttp
Опишу настройку с использованием режима Full (strict) и транспорта WS. На сервере есть сертификат, крутится веб-сервер под nginx.
Конфиг зарубежного VPS, добавим дополнительный inbound
{
"listen": "127.0.0.1", # важно, перед сервером стоит nginx, который терминирует tls и отправляет на 127.0.0.1:20000
"port": 20000,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "" # xray uuid
}
],
"decryption": "none"
},
"streamSettings": {
"network": "ws",
"security": "none", # не используем шифрование, nginx терминирует tls и передает plaintext локально
"wsSettings": {
"path": "/search" # свой путь, nginx редиректит на локальный порт запросы по этому пути
}
}
}Проверим и перезапустим
sudo xray -test
sudo systemctl restart xrayКонфиг nginx - на этом этапе у вас уже должен быть конфиг сайта с сертификатом в /etc/nginx/sites-available/mysite, в итоге должно выглядеть примерно так:
server {
listen 443 ssl http2; # managed by Certbot
server_name mysite.com www.mysite.com;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# Бекенд сайта-заглушки
location / {
proxy_pass http://localhost:7000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Vless WS - добавляем этот блок
location /search {
proxy_redirect off;
proxy_pass http://127.0.0.1:20000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}Проверим и перезапустим
sudo nginx -t
sudo systemctl restart nginxЭкспортируем добавленный инбаунд в клиент и проверим работоспобность подключения напрямую
# обратите внимание на security=tls - на клиенте нужно шифрование
vless://uuid@mysite.com:443?encryption=none&security=tls&type=ws&path=/search&host=mysite.com&sni=mysite.com&fp=chrome#VLESS-WS-TLS-CFТеперь используем его в качестве аутбаунда на промежуточном сервере.
3X-UI - Xray Configs
- Outbounds -> Add -> Json -> Link
- Routing rules -> Add -> Inbound (выберите уже настроенный инбаунд), Outbound (VLESS-WS-TLS-CF)
- Save & restart
Настроить промежуточный VPS с L4 проксированием по iptables/HAProxy на CF можно аналогично уже описанному процессу.
Upd: Добавлю рабочие конфиги xhttp + ws на nginx с self-steal.
- При включенном оранжевом облаке (CF->DNS->Records) - vps1 -> cf -> vps2 nginx -> xray
- При выключенном (рекомендую по умолчанию) - vps1 -> vps2 nginx -> xray
Таким образом можно по необходимости "подрубать" CF
nginx
server {
server_name my-site.com www.my-site.com;
listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/my-site.com-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/my-site.com-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# Legit site backend
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# vless ws
location /search {
proxy_redirect off;
proxy_pass http://127.0.0.1:20000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
# vless xhttp
location /download {
client_max_body_size 0;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_body_timeout 5m;
grpc_read_timeout 315;
grpc_send_timeout 5m;
grpc_pass unix:/dev/shm/xrxh.socket;
}
}
server {
if ($host = www.my-site.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = my-site.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name my-site.com www.my-site.com;
return 404; # managed by Certbot
}Xray-core server
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": 20000,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "uuid-ws" // replace
}
],
"decryption": "none"
},
"streamSettings": {
"network": "ws",
"security": "none",
"wsSettings": {
"path": "/search" // replace
}
}
},
{
"listen": "/dev/shm/xrxh.socket,0666",
"protocol": "vless",
"settings": {
"clients": [
{
"id": "uuid-xhttp" // replace
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"mode": "stream-one",
"path": "/download" // replace
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
]
}Xray-core client (openWRT)
{
"log": {
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log",
"loglevel": "debug"
},
"inbounds": [
{
"tag": "tproxy-in",
"protocol": "dokodemo-door",
"listen": "::",
"port": 4444,
"settings": {
"network": "tcp,udp",
"followRedirect": true
},
"streamSettings": {
"sockopt": {
"tproxy": "tproxy"
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
},
{
"tag": "test-socks",
"listen": "0.0.0.0",
"port": 1080,
"protocol": "socks",
"settings": {
"udp": true
}
}
],
"outbounds": [
{
"tag": "gavno-ws",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "vps1-ip-addr", // replace
"port": 443,
"users": [
{
"id": "uuid-ws", // replace
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"wsSettings": {
"path": "/search" // replace
},
"tlsSettings": {
"allowInsecure": false,
"serverName": "my-site.com", // replace
"fingerprint": "chrome"
}
}
},
{
"tag": "gavno-xhttp",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "vps1-ip-addr", // replace
"port": 443,
"users": [
{
"id": "uuid-xhttp", // replace
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "/download", // replace
"mode": "stream-one"
},
"security": "tls",
"tlsSettings": {
"allowInsecure": false,
"serverName": "my-site.com", // replace
"fingerprint": "chrome"
}
}
},
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
],
"routing": {
"rules": [
{
"inboundTag": "test-socks", // все с socks инбаунда на ws
"outboundTag": "gavno-ws"
},
{
"inboundTag": "tproxy-in", // все с tproxy инбаунда на xhttp
"outboundTag": "gavno-xhttp"
}
]
}
}RU vps haproxy
defaults
log global
mode tcp # L4 проксирование
option tcplog
option dontlognull
timeout connect 5000 # таймаут на установку соединения с бекенд хостами, мс
timeout client 50000 # таймаут ожидания следующих данных от клиента
timeout server 50000 # таймаут ожидания данных от сервера
frontend vless-in
bind *:443
default_backend vless-single-node
backend vless-single-node
mode tcp
server vps_eu my-site.com:443 check inter 10s fall 3 rise 2На некоторых клиентах судя по всему начали детектить фингерпринт chrome, помогает смена на firefox
Нашел интересную утилиту на бекап, пока лежал xray - https://github.com/sshuttle/sshuttle
Прокладывает туннель через ssh, проверял только на linux клиенте
# туннелит все пакеты кроме локальных и к VPS
sshuttle -v --disable-ipv6 -r user@vps-ip -x vps-ip/32 -x 192.168.0.0/16 0/0
# либо если vps сохранен в config под названием vps
sshuttle -v --disable-ipv6 -r vps -x vps-ip/32 -x 192.168.0.0/16 0/0- Примеры конфигов xray-core (github.com) - https://github.com/XTLS/Xray-examples
- Документация xray-core (github.io) - https://xtls.github.io/ru/
- Суверенный интернет, обсуждение (4pda.to) - https://4pda.to/forum/index.php?showtopic=1110469
- Глобальный форум по исследованию интернет-цензуры (github.com) - https://github.com/net4people/bbs/issues
- Русскоязычный форум по исследованию интернет-цензуры (есть только IPv6 запись днс, для доступа используйте Tor поверх VPN) (ntc.party) - https://ntc.party/
- Инструмент для раздельного туннелирования на роутерах OpenWRT (podkop.net) - https://podkop.net/
- Проверка подключения на БС и шейпинг трафика (github.com) - https://github.com/hyperion-cs/dpi-checkers








