Поднять свой VPN — это не «для гиков». Весь процесс укладывается в полчаса и стоит примерно как чашка кофе в месяц. Взамен вы получаете канал, который принадлежит только вам: его не переполняют чужие пользователи, он не продаёт вашу историю посещений и его не отключат в один день вместе с очередным популярным сервисом.
В этой инструкции — весь путь по шагам: как настроить VPN на сервере с нуля, от покупки VPS до рабочего подключения на телефоне и компьютере. Мы собрали схему VLESS + WebSocket через Cloudflare Tunnel — она сложнее в настройке, чем популярный вариант с REALITY, но у неё есть решающее преимущество: она работает там, где остальные способы отваливаются, включая мобильный интернет с блокировками. Ниже объясним, почему так, и покажем разницу на реальных замерах.
Все команды в статье выполнены на живом сервере, а не переписаны из документации: ответы, ошибки и подводные камни — настоящие.
Что понадобится
- Сервер (VPS) за пределами России — подойдёт самый дешёвый тариф, VPN почти не потребляет ресурсов.
- Домен — любой, самый дешёвый. Нужен для туннеля Cloudflare.
- Аккаунт Cloudflare — бесплатный, тарифа Free достаточно.
- 30 минут времени. Опыт администрирования не нужен: все команды даны готовыми, их достаточно скопировать.
Какой сервер выбрать
Требования минимальные — 1 ядро, 1-2 ГБ оперативной памяти, любой диск. Важнее другое:
- Локация. Чем ближе к вам физически, тем ниже задержка. Для России оптимальны Нидерланды, Германия, Финляндия, Швеция — пинг обычно 40-60 мс. США или Сингапур дадут 150-250 мс, и это заметно.
- Операционная система. Берите Ubuntu 24.04 LTS — все команды ниже написаны под неё. Debian 12 подойдёт точно так же, команды совпадают.
- IPv6. Убедитесь, что провайдер выдаёт IPv6-адрес — почти все выдают бесплатно. Он пригодится, об этом отдельный раздел ниже.
Шаг 1. Покупка сервера
Мы делали всё на Aeza — дальше все скриншоты оттуда. Подойдёт любой другой провайдер, порядок действий везде одинаковый.
Локация
Первым делом выбираем, где будет стоять сервер. В панели сразу показан ожидаемый пинг:

Москва и Петербург дают лучший пинг (40 и 54 мс), но для наших задач они не подходят — сервер должен стоять за пределами России. Оптимальный выбор по соотношению «скорость/цена» — Стокгольм или Хельсинки: 75 мс при той же цене. Франкфурт даст 85 мс, Амстердам 99 мс. Лондон обойдётся дороже остальных.
Тариф
Берём самый младший — для VPN его хватает с большим запасом:

Тариф SWEs-1: 1 ядро, 2 ГБ памяти, 30 ГБ NVMe, канал до 1 Гбит/с — 593 ₽ в месяц. Оплата возможна почасовая, 1,40 ₽ в час: удобно, если хочется сначала попробовать и не платить сразу за месяц. При оплате за три месяца дают скидку.
Операционная система

Выбираем Ubuntu 24.04 — все команды в статье написаны под неё. Debian 12 из соседнего пункта тоже подойдёт, команды совпадают.
Данные для подключения
После оплаты сервер создаётся за пару минут, и в панели появляются доступы:

Нужны три вещи: IP-адрес, логин (обычно root) и пароль. Сохраните их — без них к серверу не подключиться.
IPv6 — не пропустите
На вкладке «IP-Адреса» видно, что вместе с сервером выдаётся ещё и IPv6-подсеть:

Он достаётся бесплатно, и его стоит задействовать: ниже будет отдельный раздел о том, зачем именно IPv6 нужен и какие сервисы без него не работают.
Шаг 2. Домен и Cloudflare
Домен нужен только как «вывеска» для туннеля — сайт на нём поднимать не придётся. Подойдёт любая дешёвая зона, регистратор тоже любой: мы регистрировали у того же хостера, чтобы всё было в одной панели.

Добавляем домен в Cloudflare
Регистрируемся на dash.cloudflare.com и нажимаем Add a Site:

Вводим доменное имя, жмём Continue и выбираем тариф Free — его возможностей достаточно:

Cloudflare просканирует существующие DNS-записи и покажет их списком.
Ничего добавлять здесь не нужно. Если домен новый и список пустой — так и должно быть, смело продолжайте. Если регистратор успел создать свои записи (например, A-запись на страницу-заглушку), для VPN они не нужны — их можно удалить или просто оставить, туннелю они не мешают.
Отдельно проговорим момент, на котором новички спотыкаются: A-запись на IP вашего сервера создавать не надо. Это противоречит привычной логике «домен должен указывать на сервер», но при работе через туннель адрес сервера в DNS не публикуется вовсе — в этом и смысл. Нужную запись позже создаст сама команда cloudflared tunnel route dns, и в панели она появится с типом Tunnel и статусом Proxied. Так и должно быть, переключать её в «DNS only» нельзя — туннель перестанет работать.
Режим «DNS only» (серое облако) понадобится только в одном случае: если помимо туннеля вы держите на этом же домене что-то, работающее напрямую по IP — обычный сайт, почту или второй VPN-профиль на 443. Тогда соответствующая A-запись должна быть именно серой, иначе Cloudflare начнёт её проксировать и прямые подключения сломаются.
В конце система выдаст два адреса своих NS-серверов, вроде eloise.ns.cloudflare.com и jimmy.ns.cloudflare.com. Выпишите оба.
Меняем NS у регистратора
Заходим в панель, где куплен домен, находим настройки DNS-серверов и заменяем текущие значения на два адреса от Cloudflare:

Две подсказки, которые сэкономят вам часы ожидания:
- Адреса должны отличаться. Если случайно вписать один и тот же дважды, реестр домена молча не примет изменение, и вы будете смотреть на неизменившиеся записи, не понимая, почему ничего не происходит. Проверьте глазами, что первый и второй адрес разные.
- Смена занимает от 15 минут до нескольких часов. Панель регистратора может показывать новые значения раньше, чем обновится фактическое делегирование.
Проверить реальное состояние для доменов в зоне .ru можно так:
dig NS ваш-домен.ru @a.dns.ripn.net +norecurse
Когда в ответе появятся адреса Cloudflare — готово. Дополнительно Cloudflare пришлёт письмо об активации.
Шаг 3. Подключение к серверу
Управление сервером идёт через SSH — защищённое подключение к командной строке.
Windows: откройте PowerShell (правой кнопкой по «Пуск» → Терминал) и введите, подставив свой адрес:
ssh root@203.0.113.10
macOS и Linux: та же команда в «Терминале».
Android и iOS: установите бесплатное приложение Termius, добавьте новый хост, укажите IP, логин и пароль.
При первом подключении система спросит подтверждение подлинности сервера — введите yes. Затем введите пароль. Учтите: при вводе пароля на экране ничего не отображается, даже звёздочки — это нормально. Приглашение вида root@server:~# означает, что вы внутри.
Шаг 4. Установка Xray
Xray — программа, которая будет обслуживать ваши подключения. Сначала обновляем список пакетов и ставим то, что нужно установщику:
apt update && apt install -y curl unzip
Не пропускайте эту команду. На свежем сервере список пакетов пуст, и установщик Xray падает с сообщением error: Installation of unzip failed, please check your network — при том что с сетью всё в порядке. Мы наступили на это при проверке инструкции.
Теперь сама установка:
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
Проверяем:
xray version
Должна появиться строка вида Xray 26.3.27 ... A unified platform for anti-censorship.
Шаг 5. Настройка Xray
Нам понадобится всего одно значение — идентификатор пользователя. Он же ваш пароль от VPN:
xray uuid
Получится строка вида 06a8ca77-f130-44f4-9773-29b88e79e893. Скопируйте её в блокнот.
Открываем конфигурацию:
nano /usr/local/etc/xray/config.json
Удаляем всё содержимое (зажать Ctrl+K, пока файл не опустеет) и вставляем, подставив свой UUID:
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10000,
"protocol": "vless",
"settings": {
"clients": [ { "id": "ВАШ_UUID", "email": "main" } ],
"decryption": "none"
},
"streamSettings": {
"network": "ws",
"wsSettings": { "path": "/ws-7f3a91" }
},
"sniffing": { "enabled": true, "destOverride": ["http", "tls"] }
}
],
"outbounds": [
{ "tag": "v4", "protocol": "freedom", "settings": { "domainStrategy": "UseIPv4v6" } },
{ "tag": "v6", "protocol": "freedom", "settings": { "domainStrategy": "UseIPv6v4" } }
],
"routing": {
"rules": [
{
"type": "field",
"domain": ["domain:claude.ai", "domain:claude.com", "domain:anthropic.com"],
"outboundTag": "v6"
}
]
}
}
Сохраняем: Ctrl+O, Enter, Ctrl+X.
Обратите внимание на три детали. Слушаем только 127.0.0.1 — наружу этот порт открывать не нужно, соединение придёт изнутри через туннель. Путь /ws-7f3a91 замените на свой произвольный, чем менее очевидный, тем лучше. Про блок с двумя выходами и правилом маршрутизации — отдельный раздел ниже, пока просто оставьте как есть.
Проверяем и запускаем:
xray -test -config /usr/local/etc/xray/config.json
systemctl restart xray
systemctl status xray
Важно: именно restart, а не enable --now. Установщик уже запустил Xray со своим пустым конфигом, поэтому команда запуска ничего не сделает — сервис покажет active (running), но ваш конфиг не применится, и VPN не заработает. Это самая коварная ошибка в такой настройке: внешне всё хорошо, а порт не слушается. Проверить можно так:
ss -ltnp | grep 10000
Если строка есть — конфиг применился.
Шаг 6. Cloudflare Tunnel
Теперь связываем сервер с сетью Cloudflare. Сервер сам установит исходящее соединение — открывать входящие порты не нужно, и реальный IP сервера нигде не публикуется.
Установка
curl -sL -o /tmp/cf.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
dpkg -i /tmp/cf.deb
cloudflared --version
Авторизация
cloudflared tunnel login
Команда выведет ссылку. Откройте её в браузере, где вы вошли в Cloudflare, и подтвердите доступ для своего домена. Сертификат сам загрузится на сервер — никаких API-ключей создавать не нужно.
Нажимайте подтверждение сразу. Если потянуть несколько минут, сессия протухает, и загрузка падает с ошибкой Failed to fetch resource при полностью исправной сети. Лечится повторным запуском команды и быстрым кликом.
Создание туннеля
cloudflared tunnel create my-vpn
В ответе будет идентификатор — длинная строка вида 202d14e6-f484-4c06-bcc0-63e96faad983. Создаём конфигурацию:
nano /root/.cloudflared/config.yml
Вставляем, подставив свой идентификатор, поддомен и путь:
tunnel: ВАШ_ИДЕНТИФИКАТОР
credentials-file: /root/.cloudflared/ВАШ_ИДЕНТИФИКАТОР.json
ingress:
- hostname: vpn.ваш-домен.ru
service: http://127.0.0.1:10000
- service: http_status:404
Последняя строка обязательна: всё, кроме указанного поддомена, будет получать обычную 404.
Привязка поддомена и запуск
DNS-запись создаётся автоматически, в панель Cloudflare заходить не нужно:
cloudflared tunnel route dns my-vpn vpn.ваш-домен.ru
cloudflared service install
systemctl enable --now cloudflared
systemctl status cloudflared
Проверяем журнал:
journalctl -u cloudflared -n 20 --no-pager
Должны появиться строки Registered tunnel connection — обычно четыре, по числу соединений с ближайшими узлами Cloudflare. В них же виден город: location=fra19 означает Франкфурт.
Как это выглядит в панели Cloudflare
Зайдите в раздел DNS своего домена — там должна появиться ровно одна запись, созданная автоматически:
| Имя | Тип | Содержимое | Статус |
|---|---|---|---|
vpn.ваш-домен.ru | Tunnel | имя вашего туннеля | Proxied |
Если запись на месте и туннель показывает active — серверная часть готова полностью. Никаких других записей для работы VPN не требуется.
Шаг 7. Подключение с устройств
Собираем ссылку по шаблону, подставив свой UUID, поддомен и путь:
vless://ВАШ_UUID@vpn.ваш-домен.ru:443?encryption=none&security=tls&sni=vpn.ваш-домен.ru&fp=safari&alpn=http%2F1.1&type=ws&host=vpn.ваш-домен.ru&path=%2Fws-7f3a91#Мой-VPN
Что здесь важно:
| Параметр | Значение |
|---|---|
sni и host | ваш поддомен, оба одинаковые |
path | путь из конфига, косая черта записывается как %2F |
alpn=http/1.1 | обязателен, иначе клиент попробует HTTP/2 и WebSocket не поднимется |
flow | отсутствует — с WebSocket он не используется |
Android
Нужен v2rayNG. В Google Play его может не оказаться, поэтому надёжнее скачать APK со страницы релизов проекта на GitHub. Файлов там много — выбирайте arm64-v8a, он подходит практически всем современным телефонам:

После установки скопируйте свою ссылку, нажмите «+» в правом верхнем углу и выберите «Импорт из буфера обмена»:

Профиль появится в списке — выберите его и нажмите круглую кнопку внизу справа. Внизу экрана появится строка вида «Успешно: соединение заняло 108 мс» с указанием страны сервера.
iPhone и iPad: Streisand, V2Box или Shadowrocket — импорт из буфера обмена аналогичный.
Windows: v2rayN со страницы релизов проекта на GitHub, импорт по Ctrl+V, затем в трее выбрать режим «Глобальный».
macOS: V2Box из App Store или Hiddify.
Шаг 8. Проверка
Подключитесь и наберите в поиске «мой ip» — Яндекс сразу покажет оба ваших адреса:

Должны отображаться адреса вашего сервера, а не домашние. Подойдёт и любой другой сервис — ifconfig.co, 2ip.ru.
Обязательно проверьте оба типа сети: сначала по Wi-Fi, затем через мобильный интернет. Именно на мобильном обычно и вылезают проблемы, если они есть.
IPv6: зачем он нужен и почему в конфиге два выхода
Вернёмся к блоку outbounds с тегами v4 и v6. Это не украшение — он решает вполне конкретную проблему.
Дело в том, что часть сервисов относится к дата-центровым IPv4-адресам с подозрением. Показательный пример — приложение Claude на Android: через обычный IPv4 сервера оно не открывается вовсе, хотя тот же сайт в браузере работает нормально. Причина в том, что Cloudflare выдаёт таким адресам JS-челлендж («Just a moment...»), браузер его молча решает, а нативное приложение — нет и упирается в ошибку.
А вот на IPv6-адрес того же сервера челленджа нет. Поэтому в конфиге сделано так: весь трафик идёт по IPv4, а домены Claude — по IPv6. Проверить можно прямо через туннель:
curl --socks5-hostname 127.0.0.1:ПОРТ https://claude.ai/cdn-cgi/trace
В ответе должен быть IPv6-адрес сервера, тогда как обычные сайты будут видеть IPv4.
Как добавить свои домены в IPv6-правило
Список доменов, идущих через IPv6, вы задаёте сами. Открываем конфиг:
nano /usr/local/etc/xray/config.json
Находим блок routing и дописываем нужные домены в массив domain через запятую:
"routing": {
"rules": [
{
"type": "field",
"domain": [
"domain:claude.ai",
"domain:claude.com",
"domain:anthropic.com",
"domain:example.com",
"domain:another-service.net"
],
"outboundTag": "v6"
}
]
}
Обратите внимание на запятые: они ставятся между строками, но не после последней — это самая частая ошибка при правке, из-за неё конфиг перестаёт читаться.
Префикс перед доменом определяет, как именно он сопоставляется:
| Запись | Что попадает под правило |
|---|---|
domain:example.com | сам домен и все поддомены: example.com, api.example.com, cdn.example.com |
full:example.com | только точное совпадение, поддомены не затрагиваются |
keyword:example | любой домен, содержащий это слово |
В большинстве случаев нужен именно domain: — сервисы почти всегда используют несколько поддоменов для API, картинок и авторизации, и все они должны идти одним маршрутом. Если отправить через IPv6 только основной домен, а API останется на IPv4, приложение может вести себя странно: часть запросов проходит, часть нет.
После правки обязательно проверяем синтаксис и перезапускаем:
xray -test -config /usr/local/etc/xray/config.json
systemctl restart xray
Если вместо Configuration OK появилась ошибка — почти наверняка дело в лишней или пропущенной запятой. Xray в таком случае продолжит работать со старым конфигом до перезапуска, так что связь вы не потеряете.
Как проверить, что домен пошёл через IPv6
Для сайтов за Cloudflare есть удобная служебная страница — подключитесь через VPN и откройте в браузере:
https://ваш-домен-для-проверки/cdn-cgi/trace
В строке ip= будет адрес, которым вас видит сайт. Если там IPv6-адрес вашего сервера — правило сработало. Для сайтов без Cloudflare подойдёт любой сервис проверки IP, открытый в браузере: сравните, что он показывает при обычном сёрфинге и при заходе на нужный сервис.
Ещё один способ — посмотреть на самом сервере, каким протоколом ушло соединение:
tail -f /var/log/xray/access.log
В строках соединений будет пометка [v4] или [v6] — сразу видно, какой маршрут выбран для каждого запроса. Для этого нужен включённый лог доступа, как описано в разделе про диагностику.
Пустить весь трафик через IPv6
Если хотите пустить весь трафик через IPv6, замените блок outbounds на одну строку и удалите блок routing:
"outbounds": [
{ "protocol": "freedom", "settings": { "domainStrategy": "UseIPv6v4" } }
]
Учтите побочный эффект: сайты проверки будут показывать IPv6 вместо купленного адреса, а геолокация IPv6-подсетей у провайдеров часто отличается от IPv4 — можно оказаться «в другой стране».
Если что-то не работает
Включаем подробный лог: в конфиге меняем блок log на такой, перезапускаем Xray и пробуем подключиться.
"log": {
"loglevel": "info",
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log"
}
Смотрим, что происходит:
tail -f /var/log/xray/error.log
Основные сценарии:
| Что видим | Причина | Решение |
|---|---|---|
| в логе пусто, туннель не поднялся | проблема с cloudflared | systemctl status cloudflared, смотреть journalctl |
| подключение есть, сайты не грузятся | настройки приложения | см. ниже |
| работает и отваливается | энергосбережение Android | см. ниже |
Проверить, что туннель жив, можно и снаружи — откройте в браузере https://vpn.ваш-домен.ru. Должна открыться страница с ошибкой 404 от Cloudflare: это значит, что домен работает, туннель поднят, а путь просто не тот — так и должно быть.
Подключается, но сайты не открываются
Почти всегда причина в приложении. Проверьте три настройки:
- Проксирование по приложениям. Если включён режим «только выбранные приложения», а браузер в список не попал, через туннель пойдут лишь служебные запросы. Выглядит как полностью нерабочий VPN. Отключите.
- Режим маршрутизации — поставьте глобальный.
- Фрагментация (Fragment) — выключите. Её советуют как средство от блокировок, но здесь она только мешает.
Соединение отваливается само по себе
Если VPN работает, а потом без причины перестаёт — виновата система энергосбережения. Особенно этим грешат Xiaomi, Honor, Huawei, OPPO и Samsung: они выгружают фоновый VPN-сервис ради экономии батареи.
Разрешите приложению автозапуск и работу в фоне, снимите ограничения батареи (поставьте «Без ограничений»), а карточку приложения закрепите в списке недавних.
REALITY на 443: быстрая альтернатива, но не для всех
Если поискать инструкции по своему VPN, почти везде предлагают другую схему — VLESS + REALITY напрямую на порт 443, без домена и без Cloudflare. Она проще в настройке и быстрее в работе, потому что трафик идёт кратчайшим путём, без крюка через CDN.
Мы настроили и её на том же сервере, и вот честный результат: она заработала не на всех каналах. С одного домашнего подключения REALITY не поднялся вообще, тогда как туннель Cloudflare с того же устройства заработал сразу.
Причина — в размере первого пакета. Клиент отправляет TLS-приветствие, и его размер зависит от того, какой браузер он эмулирует. Мы замерили на живом сервере:
| Эмулируемый браузер | Размер приветствия |
|---|---|
fp=firefox | 1893 байта |
fp=chrome | 1824 байта |
fp=randomized | 517 байт |
Дело в том, что современные Chrome и Firefox добавляют в приветствие постквантовый ключ, и оно раздувается почти до двух килобайт. В обычной сети это неважно. Но на каналах с уменьшенным размером сегмента — мобильный интернет, домашние подключения через туннель провайдера, корпоративные сети — такое приветствие не помещается в один пакет и разрезается на два. И вот тут начинается самое интересное: второй кусок нередко просто не доходит. Соединение зависает, а в логе сервера появляется failed to read client hello.
Помогает параметр fp=randomized: приветствие уменьшается втрое и укладывается в один пакет. Но и это не панацея — на нашем тесте после этого соединение упёрлось уже в проверку подлинности.
Схема с Cloudflare от этой проблемы избавлена в принципе: телефон устанавливает обычное TLS-соединение с ближайшим узлом CDN, никакой особой начинки в приветствии нет, и цепляться фильтрам не за что. Поэтому в этой инструкции основным вариантом выбран именно туннель, хотя он и сложнее.
Обслуживание
Добавить пользователя
Чтобы раздать доступ близким, не отдавайте свою ссылку — сделайте отдельный профиль. Генерируем новый UUID командой xray uuid и добавляем в массив clients:
"clients": [
{ "id": "ПЕРВЫЙ_UUID", "email": "main" },
{ "id": "ВТОРОЙ_UUID", "email": "mama" }
]
После systemctl restart xray профиль заработает. Ссылка собирается по тому же шаблону, меняется только UUID.
Добавить второй сервер на тот же домен
Один домен обслуживает сколько угодно серверов — каждому свой поддомен (vpn2, vpn3 и так далее). На новом сервере повторяются шаги 4-6, а туннель создаётся с другим именем и другим поддоменом.
Полезные команды
systemctl status xray # состояние Xray
systemctl status cloudflared # состояние туннеля
systemctl restart xray # перезапуск после правки конфига
journalctl -u cloudflared -n 50 --no-pager
И правило, которое сэкономит часы: после каждого перезапуска сервера переподключайтесь в приложении вручную. Старые соединения обрываются, а клиент не всегда понимает это сам — со стороны выглядит как «внезапно перестало работать».
Когда свой VPN не подходит
Личный VPN отлично решает свою задачу — доступ к сайтам с вашего собственного канала. Но у него есть граница применимости.
Для любого сайта ваш трафик выглядит как трафик из дата-центра: IP принадлежит хостинг-провайдеру, и это видно по открытым базам. Обычному сёрфингу это не мешает, но там, где сайт оценивает, похож ли посетитель на обычного человека — регистрации, маркетплейсы, соцсети, реклама, — серверный адрес сразу выделяется. Дополнительно выдаёт TCP-отпечаток: по параметрам соединения пассивные детекторы отличают Linux-сервер от телефона, даже не заглядывая внутрь трафика.
Если задача именно в этом — выглядеть обычным пользователем мобильной сети, — нужен другой инструмент. Мобильные прокси работают через настоящие SIM-карты в сетях операторов: для удалённой стороны такой трафик и есть трафик мобильного абонента, потому что физически им и является. Как устроена такая инфраструктура изнутри, мы разбирали в материале про прокси-ферму.
Оба подхода прекрасно уживаются: свой VPN — для личного доступа к сайтам и защиты в публичных сетях, мобильные прокси — для задач, где важно, как вас видит удалённая сторона.