Блог Clearway

Как создать свой VPN-сервис: от панели до первых оплат

Коротко

Свой VPN-сервис — это четыре компонента: панель (Remnawave / Marzban / 3x-ui), нода с Xray и VLESS, ссылка-подписка и биллинг с автопродлением. Стек поднимается за вечер на VPS за 500–900 ₽/мес, и при ARPU 200 ₽ валовая маржа держится около 85–90 % — пока трафик бесплатный. Ломает экономику не железо, а гигабайты: как только ноду приходится заводить за «белый» фронт (порядка 2.5–2.8 ₽/ГБ), безлимитный клиент на 80 ГБ съедает весь свой тариф. Отсюда три обязательных решения на старте: клиенту выдаётся ссылка-подписка, а не статичный конфиг; в тарифе есть лимит трафика; «белый» вход включается адресно тем, у кого прямой IP уже не проходит.

Что входит в создание своего VPN-сервиса

Сначала карта компонентов — из неё вытекают все дальнейшие решения по деньгам и по устойчивости.

КомпонентЧто делаетГде живётПорты
Панельпользователи, лимиты, сроки, APIотдельный VPS (на старте — на ноде)443 наружу, 3000 локально
Нода (Xray-core)терминирует клиентский трафикVPS в нужной стране443 / нестандартный TLS-порт
Выдача подпискиотдаёт актуальный список конфиговрядом с панелью, свой домен443
Биллингоплата, продление, напоминанияTelegram-бот + БД
Фронт (опционально)«белый» вход перед нодойCDN / реверс-прокси443

Два правила, которые дальше сэкономят вам месяцы.

Клиенту выдаётся ссылка-подписка, а не конфиг. Ноду можно переставить, IP сменить, добавить фронт — приложение подтянет новый список само. Если вы раздали статические vless://-строки, каждая блокировка превращается в ручную рассылку и волну тикетов.

Панель и нода — разные машины, как только клиентов больше сотни. Нода под блокировками расходник, её нормально пересоздавать раз в неделю. Панель с базой пользователей и платежей — актив, терять её вместе с нодой нельзя.

Протокол по умолчанию — VLESS поверх Xray-core. Reality хорош на «чистой» связности, XHTTP нужен, когда трафик идёт через HTTP-фронт (последний раздел). WireGuard/AmneziaWG держите как запасной сценарий для стран без активного DPI, а не как основу сервиса в РФ. Пошаговая установка панели, выбор VPS и настройка Reality разобраны у нас отдельными материалами — здесь только то, что влияет на выживаемость сервиса.

Панель и первая нода: что реально нужно сделать руками

Порядок: сервер → сетевой тюнинг → панель → нода → проверка подписки.

1. Подготовка VPS (Debian 12 / Ubuntu 22.04+):

apt update && apt -y upgrade
curl -fsSL https://get.docker.com | sh
ufw default deny incoming && ufw allow 22,80,443/tcp && ufw --force enable

Важная ловушка: ufw не закрывает порты, опубликованные Docker через -p — правила проброса обрабатываются раньше цепочек ufw. Порт панели или origin-порт ноды остаётся открыт всему интернету при «включённом файрволе». Закрывать надо в DOCKER-USER либо не публиковать порт наружу вовсе:

# либо публикуем только на loopback: ports: ["127.0.0.1:8080:8080"]
iptables -I DOCKER-USER -p tcp --dport 8080 -j DROP
iptables -I DOCKER-USER -p tcp --dport 8080 -s <IP_фронта> -j ACCEPT

Это не теория: на нашем проде, пока origin-порт был открыт всем, больше половины запросов приходило мимо фронта — напрямую на IP ноды.

2. Сетевой тюнинг ноды — без него на 200+ сессиях начинаются обрывы:

cat >/etc/sysctl.d/99-node.conf <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_fastopen = 3
fs.file-max = 1000000
EOF
sysctl --system
echo '* soft nofile 1000000' >> /etc/security/limits.conf
echo '* hard nofile 1000000' >> /etc/security/limits.conf

3. Панель. Remnawave ставится docker-compose'ом, Marzban и 3x-ui — своими инсталл-скриптами; схема одинаковая. Единственное, что нужно сделать иначе, чем в большинстве гайдов, — зафиксировать версию образа тегом:

# docker-compose.yml
image: remnawave/backend:2.7.4 # не latest

Мажорные обновления панелей меняют формат выдачи подписки; с latest контейнер обновится ночью сам, и утром вы будете разбирать, почему у половины базы «перестало работать». Админские пути (/api/…, дашборд) закрывайте reverse-proxy с TLS плюс IP-фильтр или basic-auth, на отдельном поддомене.

4. Нода. В мультинодовых панелях нода — агент, получающий конфиг Xray от панели по защищённому каналу: добавляете сервер в интерфейсе, копируете сертификат/ключ, поднимаете контейнер. Руками config.json править не нужно — любые ручные правки на ноде живут до первой синхронизации агента, всю кастомизацию делайте в панели.

5. Проверка. Тестовый пользователь, подписка открыта минимум в двух клиентах (Happ + v2rayNG/NekoBox) и проверена с реального мобильного интернета РФ. «Работает у меня с зарубежного хоста» не значит ничего.

Подписка: единственная точка контакта с клиентом

Страница выдачи определяет, сможете ли вы менять инфраструктуру, не трогая клиентов. Обязательный минимум на отдаче:

location /sub/ {
 proxy_pass http://127.0.0.1:3010;
 proxy_set_header Host $host;
 proxy_hide_header ETag;
 add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
 add_header Pragma "no-cache" always;
}

Без no-store часть клиентов (по наблюдениям особенно Happ на iOS) держит старый список конфигов после переезда на новую ноду: на сервере всё живо, а в поддержку идут «не работает». Лечится либо заголовками, либо повторным добавлением подписки в приложении — второе делать руками на сотне клиентов вы не захотите.

Второй рычаг — заголовки профиля, которые понимают современные клиенты:

profile-title: base64:<название сервиса>
profile-update-interval: 6
subscription-userinfo: upload=0; download=53687091200; total=214748364800; expire=1790000000

profile-update-interval: 6 заставляет клиент перечитывать подписку каждые 6 часов — это ваш штатный канал доставки изменений, включая смену ноды. subscription-userinfo показывает остаток трафика прямо в приложении и снимает заметную долю обращений в поддержку.

Что заложить сразу:

  • Отдельные домены под подписку, панель и сайт. Заблокируют один — переезжаете, не теряя остальное. Держите 2–3 запасных имени с уже выпущенными сертификатами.
  • Длинный случайный токен в URL, без последовательных ID: /sub/8f3c…. Иначе базу подписок перебирают за вечер.
  • Несколько конфигов в одной подписке — разные ноды и транспорты. Клиенты умеют перебирать; это бесплатный фейловер до того, как вы построите нормальный.

Оплата, продления и автоматизация

Ручное продление упирается в потолок на 100–150 клиентах: дальше вы просто не успеваете переписываться.

Каналы приёма (РФ, по практике операторов): Telegram Stars и крипта (USDT TRC-20) — быстрый старт без юрлица; ЮKassa/CloudPayments через ИП или самозанятость — выше конверсия, есть возвраты, но нужна легализация; зарубежные эквайринги — для нерезидентов. Комиссию 3.5–7 % закладывайте в юнит-экономику сразу.

Бот работает с панелью только через API — никаких правок в БД панели напрямую:

# продлить пользователя после успешного платежа
curl -X PATCH https://panel.example.com/api/users/$UUID \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{"expireAt":"2026-09-01T00:00:00.000Z","trafficLimitBytes":214748364800}'

Пути эндпоинтов различаются между панелями и версиями — сверяйтесь со Swagger своей панели (/docs или /api/docs), а не с чужими гайдами; это первое, что ломается после апдейта.

Три вещи, обязательные с первого дня:

  1. Идемпотентность платежей. Ключ — ID транзакции провайдера: сохраняйте в БД и проверяйте перед начислением. Иначе повторный вебхук выдаст два месяца за один платёж, а провайдеры ретраят вебхуки штатно.
  2. Напоминания. Крон за 3 дня, за 1 день и в день окончания. По наблюдениям это самый дешёвый способ снизить отток — дешевле скидок.
  3. Ежедневная сверка. Скрипт сравнивает «оплачено до» в вашей БД и expireAt в панели, расхождения пишет в админский чат. Рассинхрон случается всегда; вопрос только в том, узнаете вы о нём от крона или от клиента.

Триал 3–7 дней поднимает конверсию, но провоцирует абьюз: привязывайте к Telegram-аккаунту, ограничивайте 5–10 ГБ и одной нодой.

Экономика: можно ли на этом зарабатывать

Вопрос «можно ли запустить свой VPN-сервис и зарабатывать» имеет численный ответ. Считаем на ARPU 200 ₽/мес и медиане потребления 60–120 ГБ на активного клиента (p90 уходит за 300 ГБ — это видео и торренты через туннель).

Статья расходов, ₽/мес100 клиентов500 клиентов
Ноды (VPS 2 vCPU / 1 Гбит)800 (×1)2 400 (×3)
Сервер панелина ноде, 0700
Домены и резервные имена150300
Эквайринг ~5 %1 0005 000
Итого~1 950~8 400
Выручка20 000100 000
Валовая маржа~90 %~92 %

Цифры красивые ровно до момента, когда ноды начинают резать и трафик приходится заводить через «белый» фронт. Ориентир по рынку — порядка 2.5–2.8 ₽/ГБ плюс разовое/фиксированное подключение около 2 500 ₽ (у разных провайдеров условия отличаются, уточняйте по своему объёму). Пересчёт:

Сценарий (100 клиентов, 80 ГБ каждый)Затраты на фронтИтоговая маржа
Белый фронт всей базе8 000 ГБ × 2.8 = 22 400 ₽отрицательная
Фронт 30 % базы2 400 ГБ × 2.8 = 6 720 ₽~55 %
Фронт 30 % базы + лимит 50 ГБ в тарифе1 500 ГБ × 2.8 = 4 200 ₽~68 %

Почему гигабайт через фронт стоит дороже обычного CDN-трафика: XHTTP в режиме packet-up дробит аплинк на множество мелких запросов, а CDN тарифицирует ещё и их количество. На масштабе плата за запросы становится отдельной статьёй расхода, и считать её надо по своей выгрузке из биллинга, а не по прайсу.

Практические выводы:

  1. Лимит трафика в тарифе — не жадность, а необходимость. 50–100 ГБ в базовом и отдельный дорогой «безлимит». Верхний дециль потребления не должен есть маржу всей базы.
  2. Белый вход включается адресно, а не всем. Технически это просто отдельный конфиг в той же подписке.
  3. Ёмкость ноды. 2 vCPU / 2 ГБ по наблюдениям тянут 150–300 одновременных активных сессий; упирается обычно не CPU, а полоса и pps. На подписчиков это 300–500 человек при активности 20–40 %. Вторую ноду ставьте на 60 % расчётной полки, а не когда первая уже деградировала.

Блокировку нод по IP закладывайте в проект с первого дня

Из-за этого раздела сервисы закрываются на третий месяц. В обычном режиме DPI борется с протоколом — против этого работают Reality, маскировка, смена SNI. Но при белых списках режется сам факт соединения с неизвестным IP дата-центра: пакет до вашего VPS не доходит вообще, и маскировка транспорта не помогает. Выпадают обычно не отдельные адреса, а диапазоны /24 целиком, вместе с соседями по хостингу.

Ответа два, и они дополняют друг друга.

1. Быстрый переезд. 2–3 подготовленные ноды (образ, ключи, готовый inbound в панели) и скрипт, переключающий подписку на живой адрес. Метрика зрелости — время от «клиенты пишут» до «подписка обновлена»: минуты, а не часы.

2. «Белый» вход перед нодой. Клиент подключается не к IP вашего дата-центра, а к адресу инфраструктуры, входящей в белые списки; фронт терминирует TLS и проксирует соединение на скрытую ноду. Это ниша whitelist-CDN как сервиса (класса Clearway): фронт заводится один раз, ноды за ним меняются свободно. Честная оговорка: «белизна» подсети не вечна — каждую /24 нужно перепроверять регулярно, а origin обязан принимать только адреса фронта (см. DOCKER-USER выше), иначе смысла в схеме нет.

Транспорт для такой схемы — XHTTP, потому что через HTTP-фронт нужно пройти обычным веб-трафиком:

"streamSettings": {
 "network": "xhttp",
 "security": "tls",
 "xhttpSettings": {
 "host": "edge.example.ru",
 "path": "/xh",
 "mode": "packet-up"
 }
}

Два факта, на которых спотыкаются почти все:

  • mode обязателен и должен быть packet-up. У Yandex CDN нет POST, поэтому аплинк XHTTP уходит методом GET, а GET допустим только в режиме packet-up. Оставите auto — часть клиентов подключится, часть нет, и вы неделю будете искать «плавающий баг».
  • extra на ноде и в клиенте должны совпадать точь-в-точь. Расхождение в этом блоке и есть классическое «в одном приложении работает, в другом нет». Держите extra минимальным: экзотические obfuscation-поля ломают совместимость между версиями Xray-core, а версии клиентов у ваших пользователей заведомо разные.

Правило простое: не «если заблокируют», а «когда». Инфраструктура проектируется под переезд сразу.

Что мерить в первый месяц и типовые грабли

Четыре цифры, без которых вы управляете вслепую:

МетрикаКак снятьОриентир
Отток (churn)доля не продливших к концу месяца> 20 % — проблема со связностью, не с ценой
ГБ на клиента, p50 / p90статистика панелиp90 > 300 ГБ — нужен лимит в тарифе
Доля клиентов на «белом» фронтепо конфигам в подпискерастёт — пересчитывайте тарифы
Время переезда нодыот алерта до обновления подпискицель — минуты

Базовая диагностика на ноде:

ss -s # число соединений, не упёрлись ли в лимиты
docker stats --no-stream # CPU/RAM агента и Xray
vnstat -d # реальный трафик по дням
journalctl -u xray -f | grep -i "rejected\|failed"

Проверка, что подписка отдаётся и не кэшируется:

curl -sI -H 'User-Agent: Happ' https://sub.example.com/sub/<token> \
 | grep -iE 'cache|profile|userinfo'

Типовые грабли на старте: панель и нода на одной машине, когда клиентов уже 300 (теряете базу вместе с сервером); latest-теги образов; один домен на всё; открытый наружу origin-порт при «включённом» ufw; отсутствие бэкапа — pg_dump базы панели по крону с выгрузкой дампа на отдельное хранилище; тестирование только с зарубежного хоста. Последнее — самая дорогая ошибка: всё, что касается блокировок, проверяется исключительно с реального мобильного интернета РФ.

Частые вопросы

Сколько клиентов держит одна нода?

По наблюдениям, VPS с 2 vCPU / 2 ГБ и аплинком 1 Гбит/с тянет 150–300 одновременных активных сессий. Узкое место обычно не процессор, а полоса и pps. При типичной активности 20–40 % от базы это 300–500 подписчиков на ноду. Цифра сильно зависит от профиля трафика: видео и торренты снижают её в разы. Вторую ноду добавляйте на 60 % расчётной полки, а не когда первая уже деградирует.

Можно ли держать панель и ноду на одном сервере?

На старте — да, это нормальный MVP. Но разносите их до того, как клиентов станет больше сотни. Нода в РФ-реалиях расходник: её блокируют, пересоздают, меняют IP. Панель с базой пользователей и историей платежей — актив; на одной машине потеря ноды означает потерю базы. Независимо от схемы делайте pg_dump базы панели по крону и увозите дамп на отдельное хранилище.

Как понять, что ноду режут по IP, а не сломался конфиг?

Проверять надо с того же мобильного интернета, где не работает. Признак блокировки по IP: TCP-хендшейк до порта ноды не устанавливается вообще — nc -vz IP 443 и curl -m 5 https://IP уходят в таймаут, при этом с зарубежного хоста и с домашнего проводного всё живо, а соседние адреса того же хостера из этой же /24 ведут себя так же. Если хендшейк проходит, а сессия рвётся через несколько секунд, это уже DPI по протоколу — лечится транспортом и маскировкой, а не переездом.

Почему один и тот же конфиг работает в одном приложении и не работает в другом?

Чаще всего дело в XHTTP-параметрах. Во-первых, mode должен быть явно packet-up: через CDN аплинк уходит методом GET (POST не проксируется), а GET допустим только в этом режиме — на auto часть клиентов подключается, часть нет. Во-вторых, блок extra на ноде и в клиенте обязан совпадать, и держать его надо минимальным: экзотические obfuscation-поля ломают совместимость между версиями Xray-core, а версии клиентов у ваших пользователей разные.

Какой лимит трафика ставить в тариф?

50–100 ГБ в базовом тарифе и отдельный дорогой «безлимит». Медиана потребления — 60–120 ГБ на активного клиента, но p90 уходит за 300 ГБ. Пока трафик бесплатен (обычный VPS с включённой полосой), безлимит терпим. Как только часть базы едет через whitelist-фронт по 2.5–2.8 ₽/ГБ, верхний дециль потребления съедает маржу всей базы за месяц.

Когда включать whitelist-фронт и всем ли клиентам?

Не всем — это ключевое решение по экономике. Прогон всей базы через белый вход при 80 ГБ на клиента стоит примерно столько же, сколько вы с неё получаете, и маржа уходит в минус. Включайте адресно тем, у кого прямое подключение к ноде уже не проходит: технически это отдельный конфиг в той же подписке. Ориентировочно 20–30 % базы под фронтом сохраняют маржу на уровне 55–70 %.

Whitelisted-вход для вашего VPN-сервиса

Clearway даёт «белый» CDN-вход перед вашей нодой — устойчивый к троттлингу операторов. Первые 10 ГБ бесплатно, без карты.

Попробовать →