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:

  1. Клиент → сервер: ClientHello

  2. Сервер отдаёт свой сертификат

  3. Prober видит: «Это не Google, это VPN»

REALITY:

  1. Клиент → VPN-сервер: ClientHello с SNI www.google.com

  2. VPN-сервер → Google: проксирует ClientHello

  3. Google → VPN-сервер: настоящий ServerHello + настоящий сертификат Google

  4. VPN-сервер → клиент: отдаёт этот реальный сертификат

  5. Клиент проверяет REALITY-авторизацию через shortId + X25519 ключ

  6. Туннель установлен

Клиент                      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.jsondocker 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 | grep

  • PostgreSQL — когда SQLite станет узким местом

Вместо заключения

VLESS+REALITY — это, на мой взгляд, лучшее, что есть сейчас для шифрования трафика. Не потому что он «самый быстрый» или «самый безопасный» в вакууме, а потому что он решает главную проблему: неотличимость от легитимного HTTPS. Всё остальное — вопрос инженерии вокруг.

Если хотите поковырять XRay — исходники открыты: XTLS/Xray-core. Документация — xtls.github.io.

Если не хотите поднимать всё самостоятельно — я на этом стеке собрал SonicVPN. Можно потыкать бесплатно (10 руб. на балансе при регистрации, хватает на ~3 дня).

Вопросы по архитектуре, gRPC, биллингу — в комментариях, постараюсь ответить.

Комментарии (37)


  1. Vasjen
    06.03.2026 21:59

    А, да. Это же легендарный твикер с непонятным встроенным клиентом впн, про который не слова.

    Интересно даже, статья, очевидно или полностью через нейронку написана, либо через нее сильно прогнана. Твикер тоже выглядит, как поделка с ее помощи написанная. Вот этот сервис с биллингом и клиентом, это все тоже последствие вайбкодинга?

    А так статья про рекламу с содержимым ни о чем: так что выяснили в итоге, сделав сервис? Как биллинг и почасовая оплата приблизи к пониманию принципов работы Vless? Как вообще угораздило рядом оказаться sqlite и grpc ? Я понимаю, что это риторические вопросы, но вдруг будут разумные ответы.


    1. 0ka
      06.03.2026 21:59

      У автора 0 комментариев


      1. Sanctuary_s
        06.03.2026 21:59

        Так это же обычные мошенники просто, которые просто нагонят трафик, не комментируя ничего. Клепают через LLM'ки статьи, и хорошо все у них.


  1. Maxim_Q
    06.03.2026 21:59

    1) Какие VPS обычно покупаете и у кого? Можно характеристики (CPU, RAM, SSD) и провайдера узнать?

    2) Сколько на одном VPS держите клиентов? Они не сильно друг другу мешают?

    3) Как вы проверяете покупателей? Что будет если клиент пойдет ломать пентагон, а к вам потом люди в погонах придут?

    4) сколько в среднем в месяц один VPS прокачивает через себя трафика?

    5) как Юкасса подключала вам прием платежей? Не ругались что вы продаете VPN, а у нас в России с ними борятся?

    6) почему не сделать сразу нужное число клиентов и не прописать их в конфиг, а потом готовые пароли просто отдавать и все? Зачем каждый раз генерить пароль для клиента и перезагружать серверную часть?


    1. danielstealth
      06.03.2026 21:59

      РКН, перелогиньтесь....)


    1. nokimaro
      06.03.2026 21:59

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

      Скрытый текст


  1. 0ka
    06.03.2026 21:59

    Цитата: "Хорошо: www.google.comwww.microsoft.comdl.google.com"

    На самом деле это ужасно. Вы явно показываете что ваш сервер это реалити прокси, никакого гугла и майкрософта на ВПС хостингах быть не может. Те, кто таким образом маскировались под whatsapp получили бан айпи адреса когда в декабре РКН начали блокировать его полностью.

    Про блокировку множества тлс соединений забыто, если её наконец нормально раскатят то всё перестанет работать.


    1. danielstealth
      06.03.2026 21:59

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


      1. Aelliari
        06.03.2026 21:59

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


  1. oleg_umnik
    06.03.2026 21:59

    Спасибо ChatGPT, ты абсолютно прав!


  1. danielstealth
    06.03.2026 21:59

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


    1. Kenya-West
      06.03.2026 21:59

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


  1. K0Jlya9
    06.03.2026 21:59

    Рефералка беспонтовая, надо хотя бы 10% с того что приносит клиент давать.


  1. 0ka
    06.03.2026 21:59

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


  1. denisemenov
    06.03.2026 21:59

    Полгода назад

    Начал с OpenVPN. Потом WireGuard. Потом Shadowsocks.

    DPI-системы провайдеров со временем учатся распознавать любой нестандартный трафик.

    Серьёзно? Они уже давно по дефолту забанены. Смысл был с них начинать?! Ох уж эти нейросоветы.


    1. 0ka
      06.03.2026 21:59

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


      1. Kenya-West
        06.03.2026 21:59

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


        1. 0ka
          06.03.2026 21:59

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


    1. Gaikotsu
      06.03.2026 21:59

      Как ни странно но тот же OpenVPN у меня без проблем работал и работает, но это возможно конечно от провайдера зависит.

      Сам я конечно юзаю vless, но иногда по необходимости ovpn использовать приходится и вполне все через него работает.


  1. pfzim
    06.03.2026 21:59

    Наконец-то попалось простое и понятное объяснение принципа работы VLESS+Reality.

    По правде говоря я не сильно гуглил эту тему.


  1. anyagixx
    06.03.2026 21:59

    Мамкины впнщики


  1. maksd_gt
    06.03.2026 21:59

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


    1. RulenBagdasis
      06.03.2026 21:59

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


      1. maksd_gt
        06.03.2026 21:59

        Здесь не согласен.

        Когда ты не рассказываешь но продолжаешь продавать свое рабочее решение - это ок.

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


    1. igors
      06.03.2026 21:59

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


  1. Vlad-Z
    06.03.2026 21:59

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


    1. Maxim_Q
      06.03.2026 21:59

      По состоянию на 2026 год OpenVPN с патчем по прежнему работает...

      Что за патч для OpenVPN где его взять и какой принцип работы?

      Были безумные попытки со стороны Ростелекома блочить все порты с 1000 по 99999

      Последний порт обычно 65535-й, у вас там похоже какой-то ультра продвинутый Ростелеком :-)


      1. Kenya-West
        06.03.2026 21:59

        Что за патч для OpenVPN

        Видимо, очередной Cloak


        1. 0ka
          06.03.2026 21:59

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


    1. sic
      06.03.2026 21:59

      А какой нужен патч? И без патча разве не работает?


    1. Kenya-West
      06.03.2026 21:59

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


  1. marginCallz
    06.03.2026 21:59

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


  1. NotSlow
    06.03.2026 21:59

    Про проблему #0 ни слова:

    Что еще за 100 руб./мес? Как это организовано? Учитывая что сейчас некоторые боятся сами себе с карты на карту переводить что-то, не говоря уж о постоянных переводах от чужих.

    Легально на р/с ИП? Как? Опять же, учитывая что даже слово vpn произносить боятся некоторые. Не говоря уж про всякие перс. данные.

    Если все в черную, за крипту например, то ладно еще, понятно. Но к чему тогда тут слово "руб"?


  1. 0x200AC
    06.03.2026 21:59

    Рубрика "беспалевно пропиарю своего платного VPN-бота"


  1. Svyat2374
    06.03.2026 21:59

    Подключился - не работает


  1. VladNi
    06.03.2026 21:59

    На самом деле самое интересное тут этот прием платежей, что бы не уехать за это :)

    Поэтому вопрос - много народу за звёзды покупает?

    И как не спалиться на юмани :)


    1. 0ka
      06.03.2026 21:59

      На юмани идёт оплата за portal-app.ru