TL;DR: Поднял VPN-инфраструктуру на VLESS+REALITY с нуля. Telegram-бот + мини-приложение, горячее управление пользователями через gRPC без рестартов XRay, балансировка между серверами, почасовой биллинг. В статье — полный разбор протокола, почему DPI его не видит, как устроена архитектура на 670+ юзеров, и все грабли, на которые я наступил в production.
Предыстория
Полгода назад я решил разобраться, как работают современные VPN-протоколы. Не на уровне «скачал WireGuard, поставил, работает», а глубже — как устроено шифрование, почему одни протоколы детектируются DPI, а другие нет, и можно ли собрать что-то своё.
Начал с OpenVPN. Потом WireGuard. Потом Shadowsocks. И каждый раз натыкался на одну и ту же проблему: DPI-системы провайдеров со временем учатся распознавать любой нестандартный трафик.
А потом я нашёл VLESS+REALITY. И понял, что это принципиально другой подход.
Спойлер: в итоге я на этом стеке собрал полноценный VPN-сервис с Telegram-ботом, мини-приложением, биллингом и балансировкой. Но обо всём по порядку.
Эволюция: почему всё до REALITY — полумеры
Прежде чем нырять в код, давайте разберёмся, почему старые протоколы проигрывают.
Поколение |
Протокол |
Как DPI его ловит |
|---|---|---|
1-е |
OpenVPN |
Характерный TLS-хендшейк с нестандартными расширениями. Паттерн виден невооружённым глазом |
2-е |
WireGuard |
Нестандартный UDP-протокол. Быстрый, но провайдер видит «непонятный UDP на фиксированном порту» — красный флаг |
3-е |
Shadowsocks |
Рандомизированные байты. Парадокс: слишком рандомизированный трафик — это аномалия. Реальный HTTPS так не выглядит |
4-е |
VMess + WS + TLS |
Уже лучше — маскировка под HTTPS. Но сервер предъявляет свой TLS-сертификат. Active probing подключается, видит self-signed серт — и всё понятно |
5-е |
VLESS + REALITY |
Предъявляет настоящий сертификат реального сайта. Active probing подключается, видит Google. Потому что это и есть Google |
Ключевое отличие REALITY: это не маскировка. Это мимикрия на уровне протокола.
Как работает REALITY: разбор по байтам
TLS-хендшейк
Обычный VPN с TLS:
Клиент → сервер: ClientHello
Сервер отдаёт свой сертификат
Prober видит: «Это не Google, это VPN»
REALITY:
Клиент → VPN-сервер: ClientHello с SNI
www.google.comVPN-сервер → Google: проксирует ClientHello
Google → VPN-сервер: настоящий ServerHello + настоящий сертификат Google
VPN-сервер → клиент: отдаёт этот реальный сертификат
Клиент проверяет REALITY-авторизацию через
shortId+ X25519 ключТуннель установлен
Клиент VPN-сервер google.com | | | |-- ClientHello (SNI:google)->| | | |-- ClientHello (SNI) ---->| | |<- ServerHello + Cert ----| |<- ServerHello + Real Cert --| | | | | | [REALITY auth: shortId | | | + X25519 public key] | | | | | |===== Encrypted tunnel ======| |
Что видит DPI:
SNI =
www.google.com— легитимныйСертификат = настоящий Google (валидный, не self-signed)
TLS fingerprint = Chrome (fingerprint эмулируется)
Паттерн = обычный HTTPS
Что видит active prober, который подключается к вашему IP:
Настоящий сайт Google. Потому что без правильного
shortIdи ключа сервер просто проксирует запрос к реальному Google.
Сервер буквально неотличим от reverse proxy. Нет правильного ключа — нет VPN. Есть ключ — есть туннель. Элегантно.
X25519: аутентификация без сертификатов
Никаких Let's Encrypt. Никаких ACME. Никаких доменов. Генерируем пару ключей:
docker exec xray-container xray x25519 # Private key: SGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Public key: Qnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Приватный на сервер, публичный — клиентам. Всё. Это X25519 ECDH — та же криптография, что в Signal и WhatsApp.
Конфиг XRay: минимум для работы
{ "inbounds": [{ "tag": "vless-reality", "port": 443, "protocol": "vless", "settings": { "clients": [], "decryption": "none" }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "dest": "www.google.com:443", "serverNames": ["www.google.com"], "privateKey": "<приватный_ключ>", "shortIds": ["abcdef1234"] } } }] }
"decryption": "none" — это не ошибка. VLESS не шифрует поверх TLS, потому что TLS и так шифрует. Двойное шифрование — это двойная нагрузка на CPU без пользы. Именно поэтому VLESS+REALITY быстрее WireGuard на реальных замерах.
Production-архитектура: от конфига до 670 пользователей
Теория закончилась. Дальше — то, ради чего я писал эту статью. Реальная архитектура production-системы, которая обслуживает 670+ пользователей на двух серверах.
Общая схема
┌─────────────────────────────────────────────────────┐ │ Telegram Bot │ │ (aiogram 3 + aiohttp) │ │ │ │ Mini-App (WebApp) ←→ API Server (порт 3000) │ │ HTML/JS/CSS aiohttp + aiosqlite │ └──────────────────────────┬──────────────────────────-┘ │ ┌────────────┼────────────┐ │ │ ┌────────┴────────┐ ┌────────┴────────┐ │ XRay Server 1 │ │ XRay Server 2 │ │ (local, gRPC) │ │ (remote, SSH) │ │ 94 устройства │ │ 49 устройств │ └──────────────────┘ └──────────────────┘
Пользователь открывает Telegram-бот → запускает мини-приложение → добавляет устройство → система генерирует UUID, выбирает наименее нагруженный сервер, добавляет пользователя через gRPC → отдаёт VLESS-ключ. Всё за 2 секунды.
Проблема #1: рестарты убивают соединения
Первая версия работала так: новый пользователь → правим config.json → docker restart xray. При каждой регистрации.
На 5 пользователях — нормально. На 50 — все текущие соединения рвутся каждый раз, когда кто-то регистрируется. Люди посреди созвона получают дисконнект. Не годится.
Решение — gRPC API XRay-core. Добавляем в конфиг:
{ "api": { "tag": "api", "services": ["HandlerService"] }, "inbounds": [{ "tag": "api-inbound", "listen": "127.0.0.1", "port": 10085, "protocol": "dokodemo-door", "settings": { "address": "127.0.0.1" } }], "routing": { "rules": [{ "inboundTag": ["api-inbound"], "outboundTag": "api", "type": "field" }] } }
Теперь добавление пользователя — один вызов, ноль рестартов, ноль дисконнектов:
# Добавить (hot, без рестарта) xray api adu --server=127.0.0.1:10085 \ -inbound=vless-reality \ -users='[{"email":"user_abc","id":"<uuid>","flow":"xtls-rprx-vision"}]' # Удалить xray api rmu --server=127.0.0.1:10085 \ -inbound=vless-reality \ -users='"user_abc"'
670 пользователей — ноль рестартов за последний месяц.
Проблема #2: gRPC не переживает рестарт
Подвох: gRPC API управляет только runtime. Перезапустил контейнер — все пользователи, добавленные через API, пропали. XRay загружает config.json, а там пусто.
Нужна синхронизация. Но писать конфиг при каждом добавлении — тоже плохо (файловый I/O, возможные race conditions). Решение — debounced sync:
_sync_task = None async def schedule_config_sync(): """Откладываем запись на 5 секунд. Новый вызов — таймер перезапускается.""" global _sync_task if _sync_task: _sync_task.cancel() _sync_task = asyncio.create_task(_delayed_sync()) async def _delayed_sync(): await asyncio.sleep(5) await write_full_config_from_db()
Пришло 10 регистраций за секунду? Один файловый write через 5 секунд после последней. А gRPC добавил всех десятерых мгновенно.
Проблема #3: второй сервер
Когда первый сервер набрал 90+ устройств, я добавил второй. И тут начинается интересное.
Локальный сервер — gRPC по 127.0.0.1. Удалённый — нужно SSH.
async def add_client(uuid: str, server: Server): user_json = json.dumps({ "email": uuid, "id": uuid, "flow": "xtls-rprx-vision" }) cmd = ( f"docker exec {server.xray_container} " f"xray api adu --server=127.0.0.1:10085 " f"-inbound=vless-reality " f"-users='[{user_json}]'" ) if server.is_local: proc = await asyncio.create_subprocess_shell(cmd, ...) await proc.wait() else: async with asyncssh.connect(server.ssh_host, ...) as conn: result = await conn.run(cmd)
Балансировка — простейший least-loaded:
SELECT s.*, COUNT(d.id) as device_count FROM servers s LEFT JOIN devices d ON d.server_id = s.id AND d.is_active = 1 WHERE s.is_active = 1 GROUP BY s.id ORDER BY device_count ASC LIMIT 1
Новый сервер стартует с 0 устройств → получает все новые регистрации → постепенно выравнивается с первым. Просто и работает.
Проблема #4: биллинг
Модель: 100 руб./мес за устройство. Но подписки я делать не хотел — слишком много головной боли с рекуррентными платежами, отменами, пропорциональным списанием.
Вместо этого — баланс + почасовое списание:
MONTHLY_COST = 100.0 # руб/мес за устройство HOURLY_RATE = MONTHLY_COST / 30 / 24 # ~0.14 руб/час async def check_balance_deductions(): """Запускается каждый час через APScheduler.""" for user, device_count in await get_users_with_active_devices(): if user.is_lifetime: continue hours = (now - user.last_deduction) / 3600 hours = max(0.5, min(hours, 48.0)) # сервер мог лежать cost = hours * device_count * HOURLY_RATE new_balance = user.balance - cost if new_balance <= 0: await set_balance(user.id, 0) await deactivate_all_devices(user.id) # gRPC remove await bot.send_message(user.id, "Баланс исчерпан!") else: await set_balance(user.id, new_balance)
max(0.5, min(hours, 48.0)) — защита от крайних случаев. Сервер лежал 3 дня? Списываем максимум за 48 часов, а не за 72. Честнее по отношению к пользователю.
При балансе на 3 дня — предупреждение. При нуле — деактивация всех устройств через gRPC API и уведомление в Telegram.
Проблема #5: шаринг ключей
VLESS-ключ — это строка. Скопировал, отправил другу, оба пользуетесь. Как бороться?
Hardware ID fingerprinting. Клиентское приложение при подключении отправляет хэш железа:
async def check_hardware(device_id: int, hw_hash: str): count = await count_unique_hw_for_device(device_id) if count >= MAX_HW_PER_DEVICE: # лимит 3 физических устройства return {"blocked": True} await upsert_hardware_id(device_id, hw_hash) return {"blocked": False}
Не серебряная пуля, но отсекает 90% абьюза.
Бенчмарк: а что со скоростью?
Замеры на одном сервере (4 vCPU, 8GB RAM, 1Gbps):
Метрика |
Без VPN |
VLESS+REALITY |
WireGuard |
OpenVPN |
|---|---|---|---|---|
Download |
940 Mbps |
910 Mbps |
880 Mbps |
650 Mbps |
Upload |
930 Mbps |
905 Mbps |
870 Mbps |
620 Mbps |
Latency |
2.1 ms |
2.3 ms |
2.4 ms |
4.8 ms |
CPU (1000 conn.) |
— |
8% |
6% |
35% |
DPI detection |
— |
Нет |
Да |
Да |
VLESS+REALITY быстрее WireGuard по throughput. Звучит контринтуитивно, но объяснение простое: VLESS не добавляет своего шифрования поверх TLS. А WireGuard шифрует всё с нуля через ChaCha20-Poly1305.
WireGuard выигрывает по CPU (kernel-space vs userspace), но в реальных условиях это не критично.
Грабли, на которые я наступил
Выбор SNI
Не каждый сайт годится. Требования:
TLS 1.3
Быстрый ответ на 443
Желательно тот же AS/хостинг (минимальный RTT)
Хорошо: www.google.com, www.microsoft.com, dl.google.com
Плохо: Cloudflare-сайты (проверяют fingerprints), маленькие сайты (подозрительно мало трафика к ним с вашего IP).
Логирование списаний
Нашёл баг через месяц после запуска: balance_history записывал amount = 0 для всех списаний. Баланс списывался корректно (через set_balance), но в историю транзакций попадал ноль. Месяц без нормальных логов.
Причина: вызывал add_balance(user_id, 0, "deduction", ...) — нужно было log_balance_history(user_id, -cost, ...).
Мораль: если у вас есть отдельная функция для set + отдельная для log — не смешивайте. Или используйте одну атомарную операцию.
SQLite в Docker
aiosqlite прекрасно работает до ~10K пользователей на одном сервере. Но:
WAL mode обязателен (без него — блокировки на каждую запись)
journal_mode=WAL+synchronous=NORMAL— золотой баланс скорости и надежностиVolumes в Docker: не монтируйте БД в tmpfs. Потерял данные один раз, хватило
VLESS-ключ: что получает пользователь
Каждому устройству генерируется уникальный UUID. Из него собирается ссылка:
vless://<uuid>@<server_ip>:443?type=tcp&security=reality &pbk=<public_key>&fp=chrome&sni=www.google.com &sid=<short_id>&flow=xtls-rprx-vision#SonicVPN
Пользователь копирует эту строку → вставляет в клиент (Hiddify, v2rayNG, v2RayTun) → подключается. Или сканирует QR-код с экрана мини-приложения.
Стек
Если кто-то захочет собрать подобное:
Компонент |
Технология |
Зачем |
|---|---|---|
VPN-ядро |
XRay-core 26.x |
VLESS+REALITY, gRPC API |
Бот |
Python + aiogram 3 |
Telegram-интерфейс |
API |
aiohttp |
REST для мини-приложения |
БД |
aiosqlite (SQLite) |
До ~10K юзеров без проблем |
Оркестрация |
Docker Compose |
XRay + бот + Caddy |
Reverse proxy |
Caddy |
Автоматический HTTPS |
Платежи |
YooKassa + Telegram Stars |
Карты, СБП, Stars |
Деплой |
Python-скрипт + paramiko |
SSH upload + docker compose up |
Что дальше
Больше серверов + гео-балансировка — сейчас least-loaded, хочу добавить выбор по GeoIP
WireGuard fallback — для случаев, когда VLESS почему-то медленнее (бывает на некоторых мобильных операторах)
Prometheus + Grafana — нормальный мониторинг вместо
docker logs | grepPostgreSQL — когда SQLite станет узким местом
Вместо заключения
VLESS+REALITY — это, на мой взгляд, лучшее, что есть сейчас для шифрования трафика. Не потому что он «самый быстрый» или «самый безопасный» в вакууме, а потому что он решает главную проблему: неотличимость от легитимного HTTPS. Всё остальное — вопрос инженерии вокруг.
Если хотите поковырять XRay — исходники открыты: XTLS/Xray-core. Документация — xtls.github.io.
Если не хотите поднимать всё самостоятельно — я на этом стеке собрал SonicVPN. Можно потыкать бесплатно (10 руб. на балансе при регистрации, хватает на ~3 дня).
Вопросы по архитектуре, gRPC, биллингу — в комментариях, постараюсь ответить.
Комментарии (37)

Maxim_Q
06.03.2026 21:591) Какие VPS обычно покупаете и у кого? Можно характеристики (CPU, RAM, SSD) и провайдера узнать?
2) Сколько на одном VPS держите клиентов? Они не сильно друг другу мешают?
3) Как вы проверяете покупателей? Что будет если клиент пойдет ломать пентагон, а к вам потом люди в погонах придут?
4) сколько в среднем в месяц один VPS прокачивает через себя трафика?
5) как Юкасса подключала вам прием платежей? Не ругались что вы продаете VPN, а у нас в России с ними борятся?
6) почему не сделать сразу нужное число клиентов и не прописать их в конфиг, а потом готовые пароли просто отдавать и все? Зачем каждый раз генерить пароль для клиента и перезагружать серверную часть?

nokimaro
06.03.2026 21:59п.5 при переходе на платежный шлюз видно что используется прокладка в виде portal-app[.]ru
Скрытый текст


0ka
06.03.2026 21:59Цитата: "Хорошо:
www.google.com,www.microsoft.com,dl.google.com"На самом деле это ужасно. Вы явно показываете что ваш сервер это реалити прокси, никакого гугла и майкрософта на ВПС хостингах быть не может. Те, кто таким образом маскировались под whatsapp получили бан айпи адреса когда в декабре РКН начали блокировать его полностью.
Про блокировку множества тлс соединений забыто, если её наконец нормально раскатят то всё перестанет работать.

danielstealth
06.03.2026 21:59Ну тут палка о двух концах, берёте дедик где-нибудь в дата центре Хельсинки, там у того же гугла и мелкософта сервера есть, прописывете обратную ptr запись гугллвскую (или несколько разных) и вперёд. Множество tls соединений вообще ни о чём не говорит ни разу. И слава богу, в ркн это понимают, а если перестанут понимать, то половина рунета снова ляжет, как в 2018 году когда телегу "блокировали" первый раз.

Aelliari
06.03.2026 21:59AS будет не гугловская. Стоимость аренды железа у самого Гугла - уже неприятная

danielstealth
06.03.2026 21:59Поздравляю, автор, ты изобрёл очередной Marzban ) и до кучи как-то криво с помощью бесплатной llm...

Kenya-West
06.03.2026 21:59Remnawave лучше) как бывший пользователь Marzban говорю. Но долго я им не пользовался, в итоге соскочил на Ремну.

0ka
06.03.2026 21:59Да чёрт, как статья с заминусованой стала заплюсованой? Тут реально почти ничего полезного нет

denisemenov
06.03.2026 21:59Полгода назад
Начал с OpenVPN. Потом WireGuard. Потом Shadowsocks.
DPI-системы провайдеров со временем учатся распознавать любой нестандартный трафик.
Серьёзно? Они уже давно по дефолту забанены. Смысл был с них начинать?! Ох уж эти нейросоветы.

0ka
06.03.2026 21:59Ну первые два это всё таки классика и существуют обходы блокировки, я ими до сих пор пользуюсь

Kenya-West
06.03.2026 21:59Если вы про AWG 2.0, то он давно умеет в XRay, насколько я знаю. Так что там от стандартного WG ничего не осталось.

0ka
06.03.2026 21:59AWG - WG это сокращение wireguard, какой xray? Есть amnezia VPN который умеет xray, но это совсем другое

Gaikotsu
06.03.2026 21:59Как ни странно но тот же OpenVPN у меня без проблем работал и работает, но это возможно конечно от провайдера зависит.
Сам я конечно юзаю vless, но иногда по необходимости ovpn использовать приходится и вполне все через него работает.

pfzim
06.03.2026 21:59Наконец-то попалось простое и понятное объяснение принципа работы VLESS+Reality.
По правде говоря я не сильно гуглил эту тему.

maksd_gt
06.03.2026 21:59Автор, побольше статей. Чтобы ркн быстрее сконцентрировался на блоке влесс. Не понимаю я вас, чего вы из штанов выпрыгиваете всем рассказать какие вы молодцы и регулятор вас не видит. Ты же провоцируешь просто этого самого регулятора. Ну поднял ты влесс, ну молодец, не ты один такой, зачем кричать то об этом на каждом углу?

RulenBagdasis
06.03.2026 21:59Цель роскомзапрета заблокировать интернет большинству пользователей. Если вы знаете, как обойти запрет, но не рассказываете никому, вы играете на стороне властей.

maksd_gt
06.03.2026 21:59Здесь не согласен.
Когда ты не рассказываешь но продолжаешь продавать свое рабочее решение - это ок.
Когда ты начинаешь кричать об этом на каждом углу, ты по сути становишься маркером. Это из разряда доносов. Ты кричишь "у вас здесь дырка, обратите внимание".

igors
06.03.2026 21:59А то они про него не знают. Они и гугл на впске давно уже научились блочить. :-)

Vlad-Z
06.03.2026 21:59По состоянию на 2026 год OpenVPN с патчем по прежнему работает, wg, awg, vless тоже работают. Были безумные попытки со стороны Ростелекома блочить все порты с 1000 по 99999, надеюсь это был разовый приступ. Все эти протоколы тоже не стоят на месте и развиваются, иногда достаточно просто обновить библиотеки

Maxim_Q
06.03.2026 21:59По состоянию на 2026 год OpenVPN с патчем по прежнему работает...
Что за патч для OpenVPN где его взять и какой принцип работы?
Были безумные попытки со стороны Ростелекома блочить все порты с 1000 по 99999
Последний порт обычно 65535-й, у вас там похоже какой-то ультра продвинутый Ростелеком :-)

Kenya-West
06.03.2026 21:59Что за патч для OpenVPN
Видимо, очередной Cloak

0ka
06.03.2026 21:59Нет, мусор и манипуляция с первыми пакетами. На github gubernievs в контейнере есть если не ошибаюсь

Kenya-West
06.03.2026 21:59То, что это работает у вас, не значит, что это работает у 99% остальных. Да, внутри РФ большинство протоколов пока работает, но эта недоработка в любой момент может быть исправлена.

marginCallz
06.03.2026 21:59А если active prober проверит, есть ли такой IP в DNS записях google.com, ничего не обнаружит и забанит?

NotSlow
06.03.2026 21:59Про проблему #0 ни слова:
Что еще за 100 руб./мес? Как это организовано? Учитывая что сейчас некоторые боятся сами себе с карты на карту переводить что-то, не говоря уж о постоянных переводах от чужих.
Легально на р/с ИП? Как? Опять же, учитывая что даже слово vpn произносить боятся некоторые. Не говоря уж про всякие перс. данные.
Если все в черную, за крипту например, то ладно еще, понятно. Но к чему тогда тут слово "руб"?
Vasjen
А, да. Это же легендарный твикер с непонятным встроенным клиентом впн, про который не слова.
Интересно даже, статья, очевидно или полностью через нейронку написана, либо через нее сильно прогнана. Твикер тоже выглядит, как поделка с ее помощи написанная. Вот этот сервис с биллингом и клиентом, это все тоже последствие вайбкодинга?
А так статья про рекламу с содержимым ни о чем: так что выяснили в итоге, сделав сервис? Как биллинг и почасовая оплата приблизи к пониманию принципов работы Vless? Как вообще угораздило рядом оказаться sqlite и grpc ? Я понимаю, что это риторические вопросы, но вдруг будут разумные ответы.
0ka
У автора 0 комментариев
Sanctuary_s
Так это же обычные мошенники просто, которые просто нагонят трафик, не комментируя ничего. Клепают через LLM'ки статьи, и хорошо все у них.