Самостоятельный VPN-сервис с веб-панелью: одна entry-нода в России, одна или несколько exit-нод за рубежом, три протокола для клиентов (WireGuard, OpenVPN, IKEv2), маршрутизация по географии, DNS с фильтрацией рекламы и Telegram-бот. Ставится одной командой на чистые серверы.
🚀 Сразу деплоить → Quickstart
Обзор: события панели, трафик за сутки, нагрузка на экзиты, DNS с долей блокировок. Ниже - карточки нод (CPU, iowait, RAM, статус), календарь активности клиентов, топ клиентов по трафику и счётчики онлайн/офлайн.
Клиенты и ключи: список с трафиком за сегодня, экзитом и сроком доступа,
фильтры «истекают» и «заморожены», рассылка от бота. Справа - карточка
клиента: срок, суммарный трафик, привязка к Telegram-боту, лимит устройств,
предпочитаемый экзит. Снизу - устройства с трафиком и кнопками: WG
(.conf/QR), OVPN (.ovpn), IKEv2 (сервер, Remote ID, логин, пароль и CA
при работе без домена), перевыпуск ключей, удаление; заморозка и удаление
клиента.
- Три протокола. WireGuard, OpenVPN и IKEv2/IPsec на одной entry-ноде. IKEv2 работает штатными клиентами iOS, macOS, Windows и Android без сторонних приложений.
- Клиенты и устройства. Клиент - это человек, у него несколько устройств. У каждого устройства свои ключи WireGuard, сертификат OpenVPN и логин IKEv2. Лимит устройств, срок доступа, заморозка, перевыпуск ключей - из панели или из бота.
- Маршрутизация по географии. Российский трафик уходит напрямую с entry-ноды, зарубежный - через Hysteria2 на лучший экзит. Экзит падает - сторож переключает на другой за 10-14 секунд, живые соединения не рвутся. Подробности: docs/routing.md, docs/failover.md.
- Ручные правила. Домен, IP или подсеть на конкретный экзит, перетаскиванием в панели. Применяется на ходу, без перезапуска и без обрыва соединений.
- Предпочитаемый экзит клиента. Весь зарубежный трафик клиента через выбранную страну. Экзит пропал - трафик временно идёт через общий выбор, вернулся - возвращается сам.
- YouTube без рекламы (опционально). YouTube выводится с российского адреса entry-ноды, где реклама не показывается, а блокировка DPI обходится десинхронизацией TLS (zapret2). Видеопоток проверяется каждые пять минут; если обход сломался, YouTube уходит на экзиты. Подробности: docs/youtube.md.
- Локальная сеть не ломается. Домашние адреса (RFC1918, CGNAT, multicast) не уезжают в туннель: роутер, NAS, принтер и Chromecast остаются доступны при поднятом VPN.
- DNS с фильтрацией. AdGuard Home для клиентов с включённой фильтрацией, управление - во вкладке «DNS» панели. Подробности: docs/adguard.md.
- Telegram-бот. Администратор управляет клиентами и получает алерты. Конечный клиент по ссылке-приглашению сам скачивает конфиги, добавляет устройства и видит уведомления.
- Мониторинг. Нагрузка нод, трафик за сутки, задержка до экзитов, активность клиентов - в панели.
- Резервные копии. Один зашифрованный архив со всем нужным для восстановления entry-ноды, по расписанию, локально или в S3. Подробности: docs/backup.md.
- Вход в панель. Один администратор, по желанию TOTP или SSO через OIDC-провайдера (проверено на Authentik): docs/sso.md.
клиент WireGuard · OpenVPN · IKEv2
│
▼
entry-нода (Россия)
├─ WireGuard / OpenVPN / strongSwan → sing-box (TPROXY)
├─ sing-box: ручные правила → YouTube → РФ напрямую → остальное на экзит
├─ сторож экзитов: пробы, переключение, балансировка
├─ zapret2: обход DPI для YouTube
├─ панель (Caddy, Let's Encrypt), AdGuard Home, VictoriaMetrics, бот
│
└─ Hysteria2 ──► exit-нода nl / fi / us / … ──► интернет
Exit-ноды связаны с entry-нодой туннелями Hysteria2 и служебной mgmt-сетью. У каждого экзита два пути: прямой и через Cloudflare WARP. Полная схема правил - docs/routing.md.
Стек. Nuxt 4 (Vue 3, TypeScript) · Nuxt UI 4 · Drizzle ORM + SQLite ·
better-auth · sing-box · WireGuard · OpenVPN · strongSwan · AdGuard Home ·
VictoriaMetrics + node_exporter · Caddy · Python (python-telegram-bot).
Структура репозитория.
app/ панель (Nuxt 4)
infra/ деплой: стадии, шаблоны конфигов, сторож экзитов, тесты
telegram/ бот
deploy.sh точка входа деплоя
setup.sh интерактивный мастер config.yaml
config.example.yaml
docs/ инструкции и скриншоты
Документация.
| Файл | О чём |
|---|---|
| docs/routing.md | порядок правил sing-box, ручные маршруты, сплит-туннель, IPv6, почему не ходит ping, QUIC |
| docs/failover.md | сторож экзитов: пороги, дорожки и балансировка, выход бота, ручное выключение экзита |
| docs/youtube.md | YouTube мимо экзитов: отбор трафика, стратегия десинка, A/B-проверка, видеопроба |
| docs/backup.md | состав архива, хранение, ротация, восстановление entry-ноды с нуля |
| docs/adguard.md | AdGuard Home: прямой вход, учётка, лимиты |
| docs/sso.md | вход в панель через OIDC (Authentik) |
| docs/speedtest.md | BBR и Brutal на Hysteria2, замер скорости через туннель на стенде |
Нужны: одна entry-нода в России и N exit-нод (минимум одна, лучше две-три для отказоустойчивости). Чистая Ubuntu 24.04 или 26.04, root по SSH-паролю. Деплой запускается с entry-ноды, она же оркестратор.
| Нода | Минимум | Рекомендуется |
|---|---|---|
| entry | 1 vCPU, 2 ГБ RAM, 10 ГБ | 2 vCPU, 2 ГБ RAM, 20 ГБ |
| exit | 1 vCPU, 1 ГБ RAM, 5 ГБ | 1 vCPU, 2 ГБ RAM, 10 ГБ |
Entry-нода упирается в процессор: один vCPU занят полностью уже на ~120 Мбит/с суммарно. Swap не нужен, лимиты служб подбираются по памяти.
0. Домен (если нужен HTTPS). Направьте A-запись vpn.example.com → <entry-ip> заранее: DNS расходится до четырёх часов, иначе Caddy не
получит сертификат при первом запуске. Только A-запись, без AAAA.
1. Зайти на entry-ноду.
ssh root@<entry-ip>Если до этого сделать ssh-copy-id root@<entry-ip>, деплой подхватит ваш
ключ и разложит его на все exit-ноды. Без этого оркестратор сгенерирует
свой.
2. Склонировать репозиторий.
git clone https://github.com/anysda/vpn2 /opt/anysda-vpn2
cd /opt/anysda-vpn23. Заполнить конфиг мастером. Ответы попадут в config.yaml.
./setup.sh4. Запустить деплой.
./deploy.shДеплой ставит зависимости, проверяет SSH ко всем нодам, поднимает mgmt-сеть и Hysteria2 на exit-нодах, а на entry-ноде - VPN-серверы, роутер, сторож, AdGuard, мониторинг, панель и бота. Повторный запуск безопасен: стадии идемпотентны.
5. Создать первого клиента. В панели «Новый клиент» → имя → в карточке
клиента «+ девайс» → имя. Дальше кнопки WG (.conf или QR), OVPN, IKEv2.
Пароль администратора, если не задан в конфиге, генерируется при первом
деплое и лежит в /etc/anysda/admin-password.txt на entry-ноде.
Всё задаётся в config.yaml, образец с комментариями -
config.example.yaml. После правки конфига
перезапустите ./deploy.sh целиком или нужную стадию.
ports:
wireguard: 51820 # UDP
openvpn: 1194 # UDP или TCP
openvpn:
proto: udp # udp | tcp, с суффиксами 4/6
wireguard:
mtu: 1420 # 1280..9000, MTU клиента и MSS сервера
split_local: true # обход локальных сетей клиента (WG и OpenVPN)Применить: ./deploy.sh 28-wireguard ru, ./deploy.sh 29-openvpn ru,
./deploy.sh 30-frontend ru. После смены порта, протокола, MTU или обхода
локальных сетей клиентам нужно скачать конфиги заново, ключи перевыпускать
не нужно. Порт должен быть открыт во внешнем firewall хостера.
Укажите panel.domain, направьте A-запись и запустите ./deploy.sh.
Caddy получит сертификат Let's Encrypt, панель перейдёт на HTTPS. IKEv2 до
этого работает по IP со своим CA, после - автоматически переключается на
домен и доверие к Let's Encrypt. Профили IKEv2, выданные до подключения
домена, нужно скачать заново: меняются сервер и Remote ID, логин и
пароль остаются. Диагностика: journalctl -u anysda-ikev2-cert-sync -u caddy.
youtube:
route: zapret # off | zapret | direct
quic: block # QUIC режется намеренно, десинк проверен на TCPПроверка - только A/B, статус юнита ничего не доказывает:
ssh root@<entry> anysda-yt-check # 0 = обход живой
ssh root@<entry> 'journalctl -u anysda-yt-failover' # журнал видеопробыЕсли обход не проходит: youtube.offload: off, подобрать стратегию через
/opt/zapret2/blockcheck2.sh и записать в youtube.strategy, либо
youtube.route: off. Как это работает и что проверяет видеопроба -
docs/youtube.md.
Раз в две секунды сторож пробует каждый экзит. Три промаха подряд - экзит считается мёртвым, новые соединения идут на другой, живые дорабатывают на старом. Устройства разложены по 32 дорожкам, сторож держит каждую на быстром экзите и выравнивает нагрузку. Экзит можно выключить вручную в карточке экзита в панели. Пороги, балансировка и выход бота - docs/failover.md.
Стадии 20 (роутер) и 21 (сторож) обновляйте вместе:
./deploy.sh 20-ru-router ru && ./deploy.sh 21-failover-watchdog ru./deploy.sh 30-frontend ru # пересобрать панель
./deploy.sh 35-telegram ru # перезапустить бота
./deploy.sh 10-foreign foreign # все exit-ноды
./deploy.sh 19-yt-zapret ru # обход DPI для YouTube
./deploy.sh 99-verify all # проверка после деплояПолный список стадий - infra/scripts/.
./deploy.sh backup снимает один зашифрованный архив со всем, что нужно
для восстановления entry-ноды: база панели, секреты /etc/anysda,
серверный ключ WireGuard, CA и PKI OpenVPN, сертификаты IKEv2, хранилище
Caddy. Архив шифруется age до
записи на диск.
./deploy.sh backup # снять архив сейчас
./deploy.sh backup-list # список архивов
./deploy.sh restore # восстановить последний
./deploy.sh restore <archive> --force # перетереть непустую базуПо умолчанию архивы снимаются раз в сутки в 02:00 и хранятся в
/var/backups/anysda-vpn2/ (последние 10). Для настоящего disaster
recovery архивы должны жить вне entry-ноды: S3 у другого провайдера или
синхронизация на удалённое хранилище.
config.yaml - мастер-секрет. В нём пароли всех нод, пароль панели,
токен бота, ключ оркестратора и passphrase архивов. Без passphrase архивы
нечитаемы. Храните копию вне entry-ноды, chmod 600, не коммитьте.
Состав архива, хранение в S3, ротация и пошаговое восстановление entry-ноды с нуля - docs/backup.md.
Опционален. Включается, если заданы telegram.bot_token и
telegram.chat_id (в setup.sh или в настройках панели). Админский чат -
личка с ботом или приватная группа с выключенным Group Privacy.
| Команда | Что делает |
|---|---|
/clients |
список клиентов, создание, карточка с действиями |
/status |
состояние нод и задержка до экзитов |
/nodes |
список exit-нод с RTT |
В карточке клиента: заморозить, лимит устройств, срок, приглашение,
удалить. «Отправить приглашение» даёт ссылку t.me/<bot>?start=… для
пересылки клиенту: по ней бот привязывает его чат, и дальше клиент сам
скачивает конфиги, добавляет устройства и получает уведомления об
изменениях. «Отозвать доступ к боту» в панели отвязывает Telegram и
делает старую ссылку недействительной.
Бот не перевыпускает ключи, не трогает маршруты и не меняет свои настройки - это только в панели.
MIT.

