Как повысить надежность VPN доступа для домашней сети, если в случае блокировки VPN нет возможности в разумный срок обновить конфиг и восстановить доступ?
Решение ― multi‑VPN резервированием VPN‑провайдеров, использующих разные VPN‑протоколы. Собственный опыт, собранный в виде гайда.
Предыстория
Недавно мне нужно было настроить доступ по VPN родственникам в пригороде, фактически деревня. Интернет от местного провайдера более‑менее стабильный, но пользователи, в основном, люди в возрасте, а сам я живу за границей и приезжаю очень редко. В случае проблем с блокировкой, поменять новый конфиг будет некому (по крайней мере, в разумный срок). Разумеется, я заранее настроил удаленный доступ, но нет гарантии, что завтра он сам не окажется заблокированным. В доме есть сразу несколько устройств, которым нужен VPN, плюс, периодически приходят гости со своими смартфонами.
Поэтому обычный подход «пропал доступ ― применил новый конфиг» в моем случае может не сработать.
С такими вводными я задумался об архитектуре решения. Со стороны сервера всё довольно просто: так как ранее у меня уже был арендованный VPS вне России, решил сделать self‑hosted решение.
Со стороны клиентов самое простое было бы установить VPN‑клиент на каждое устройство, но управление таким зоопарком приводит в уныние, обновить VPN‑конфиг становится сложно, да и гостевые девайсы покрыть не получится. Очевидный выход ― это VPN на роутере. Я давний пользователь Mikrotik, поэтому выбор роутера для реализации плана очевиден.
Введение
Дано: домашняя сеть устройств, плюс периодически появляющиеся гостевые устройства. Оперативного доступа к настройке VPN клиентов на девайсах нет. И весьма затруднительно попросить пользователей самостоятельно обновить конфиг.
Задача: повысить надежность VPN в случае блокировок с такими вводными.
Решение: использование нескольких VPN каналов с резервированием на роутере.
В этом гайде мы настроим сразу два клиента VPN, AmneziaWG и VLESS+Reality, работающих одновременно как основной и резерв на одном Mikrotik роутере с общим раздельным туннелированием (split‑routing). Хотя таких каналов может быть столько, сколько позволят ресурсы роутера. Этакий VPN‑box, но с резервированием.
Резервирование повышает надежность VPN доступа в случае падения каналов (что периодически происходит сегодня с популярными VPN сервисами). Однако, если все каналы перестанут работать, то ничего не останется, как получить доступ к роутеру для настройки.
Однако, если у нас развёрнут self‑hosted VPN, то появляются дополнительные возможности исправить ситуацию без получения доступа. Про настройку self‑hosted VPN ниже есть отдельный раздел.
Зачем ещё один гайд по VPN на микротике? Мне нужно было настроить VPN с резервированием, имея арендованный сервер, начальное поверхностное понимание используемых технологий обхода блокировок (OpenVPN сервер раньше настраивал разве что) и ограниченный запас времени. Гайды в сети весьма разрозненны и предполагают разный уровень погружения в тему. Многие из них написаны для старых версий RouterOS (хотя различия минимальны) или только для OpenWRT/Linux. ChatGPT и Claude сгенерировали полурабочие конфигурации, но подсказали направление куда копать.
Поэтому я решил описать свой опыт, собрав все в одном месте в виде гайда.
Архитектура
С клиентом всё просто: нужен Mikrotik, поддерживающий контейнеры (то есть. RouterOS на архитектурах arm64, arm32 или x86), см. таблицу продуктов. В примере используется Mikrotik hap ax2 (C52iG-5HaxD2HaxD) — бюджетное (хоть не самое дешевое) решение с диском 128 МБ (свободно ~80 МБ на старте, но нам хватит) и RAM 1ГБ.
Как сказано выше, этого достаточно для резервирования, но self‑hosted VPN даёт нам дополнительные опции.
Идея простая: выдаем одному (или нескольким) пользователям по домену на каждый канал VPN. Если блокируют IP, то мы просто переносим VPS на новый IP и обновляем DNS, не меняя конфиги на клиентах. Такая схема дороже, чем обращение просто по IP, поэтому не подходит тем, у кого много пользователей, но зато повышает надежность канала и обеспечивает «необслуживаемость» немногочисленных клиентов.
Для нашего примера, также, сделаем простую базовую архитектуру: один VPS с одним внешним IP и одним доменом, и два протокола: AmneziaWG и VLESS+Reality. Хотя, конечно, надежнее будет разнести VPN на два разных сервера и на два разных домена у двух разных хостеров, но для нашего примера сойдет.

VPS
Наиболее простая конфигурация: 1 внешний IP, домен (будем использовать example.com для примера), указывающий на него. Также, мы не используем Docker и не настраиваем IPv6. Пример взят для Debian 12, но в других дистрибутивах всё аналогично.
Все команды выполняются от root.
AmneziaWG
Установка
Приложение Amnezia умеет разворачивать сервер автоматически в Docker, это удобно и тоже рабочий вариант. Но здесь мы не будем использовать Docker, а установим всё нативно.
Воспользуемся amneziawg‑installer:
curl -O https://raw.githubusercontent.com/bivlked/amneziawg-installer/master/install\\_amneziawg.sh chmod +x install_amneziawg.sh sudo bash ./install_amneziawg.sh
Инсталлятор задаст несколько вопросов, автоматически настроит фаервол и параметры ядра, в процессе потребует перезагрузки VPS.
Важно: В качестве endpoint указывайте ваш домен, а не IP‑адрес. Тип установки нужно выбрать «Весь трафик», так как split‑routing мы будем настраивать независимо на стороне Mikrotik.
Управление пользователями и конфиги
Добавление/удаление пользователей: /root/awg/manage_amneziawg.sh add и /root/awg/manage_amneziawg.sh remove.
Посмотреть состояние: awg show
Конфиги клиентов лежат в /root/awg/*.conf, ссылки vpn:// в /root/awg/*.vpnuri, QR‑коды в /root/awg/*.png.
Логи
Логи подключений:
journalctl -u awg-quick@awg0
Также, debug‑логирование модуля ядра amnezia включается командой:
echo "module amneziawg +p" > /sys/kernel/debug/dynamic_debug/control
Далее, смотрим debug‑логи через dmesg или journalctl -k.
Миграция на другой сервер
На старом сервере создаем бекап:
/root/awg/manage_amneziawg.sh backup
Команда создаст архив в /root/awg/backups/, содержащий конфиг сервера, конфиги клиентов и ключи. Далее, на новом сервере восстанавливаем бекап:
/root/awg/manage_amneziawg.sh restore backup.tar.gz
VLESS+Reality
Установка
Используем sing‑box:
curl -fsSL https://sing-box.app/install.sh | sh
Скрипт скачает и установит deb пакет и создаст systemd‑сервис. Также, понадобятся дополнительные пакеты:
apt install ufw openssl curl
Настраиваем параметры ядра. Создадим файл /etc/sysctl.d/99-user.conf и пишем следующее:
net.ipv4.ip_forward = 1 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 net.core.default_qdisc=fq net.ipv4.tcp_congestion_control=bbr kernel.printk = 3 4 1 3
Применяем:
sysctl --system
Настройка фаервола (закрываем все порты на вход, кроме ssh и https, на котором будет VPN):
ufw default deny incoming ufw default allow outgoing ufw allow ssh/tcp ufw limit ssh/tcp ufw allow https/tcp ufw enable
Конфигурация
Конфигурация сервера и добавление пользователей делается в файле /etc/sing-box/config.json.
Sing‑box ― это прокси сервер с поддержкой многих протоколов, поэтому основные секции конфига это inbounds и outbounds. Нам нужен простой прокси: сервер принимает VLESS+Reality подключения на порту 443 и проксирует их наружу как обычные соединения.
Генерируем пару ключей Reality (PublicKey из вывода понадобится далее для настройки клиентов):
sing-box generate reality-keypair
Генерируем short_id. Это рандомная hex‑строка длиной до 8 байт, используемая сервером во время TLS‑handshake для того, чтобы отличать VPN‑подключения от активного пробинга DPI и др., своего рода fingerprinting. Short_id можно создать несколько, но нам хватит одного:
openssl rand -hex 8 # Например: 2b1f1c1475f38e06
Генерируем UUID пользователя:
sing-box generate uuid
Сайт для маскировки можно выбрать любой, поддерживающий TLS 1.3 и HTTP/2: google.com, microsoft.com и тому подобное
Теперь все значения полученные выше (PrivateKey, short_id, UUID пользователя и имя сайта) сводим в конфиг /etc/sing-box/config.json:
{ "log": { "level": "info" }, "dns": { "servers": [{ "type": "udp", "server": "1.1.1.1", "server_port": 53 }] }, "inbounds": [ { "type": "vless", "listen": "0.0.0.0", "listen_port": 443, "users": [ { "uuid": "bb5b0d3d-931f-44cb-879b-e630c68dd2ec", "flow": "xtls-rprx-vision", "name": "myuser" } ], "tls": { "enabled": true, "server_name": "google.com", "reality": { "enabled": true, "handshake": { "server": "google.com", "server_port": 443 }, "private_key": "8D33x7XZpFsnxKip4LN5cj0nD5jcqMjNTEW23cl9vEQ", "short_id": ["2b1f1c1475f38e06"] } }, "multiplex": { "enabled": true } } ], "outbounds": [{ "type": "direct" }] }
Перезапускаем сервер:
―
Логи
Логи сервера смотрим через journalctl:
journalctl -u sing-box
Миграция на другой сервер
Достаточно перенести /etc/sing-box/config.json на новый сервер и перезапустить:
systemctl restart sing-box
Клиент на Mikrotik
Цель раздела — настроить Mikrotik для следующего:
Основной VPN + горячий резерв. При падении основного канала трафик автоматически уходит через резервный. При падении всех каналов трафик идёт напрямую. При восстановлении канала трафик снова идёт через рабочий канал.
Split‑routing: трафик к заблокированным ресурсам идёт через VPN, остальное напрямую. Также, нужно автообновление списков подсетей
DNS‑over‑HTTPS для защиты от подмены DNS
Опционально: LED‑индикация состояния туннелей
Опционально: физическая кнопка «VPN вкл/выкл» на корпусе
Основным каналом назначим AmneziaWG, резервным VLESS+Reality (хотя можно как угодно). Манипуляции будем производить на последней стабильной версии RouterOS на момент написания 7.23.3.
Предварительная настройка
Для работы с контейнерами нужен пакет RouterOS под названием container. Как устанавливать пакеты написано в Официальной документации.
Включаем поддержку контейнеров в device‑mode. Также, нам понадобится планировщик и утилита fetch (после выполнения команды ниже роутер запросит перезагрузку или нажатие физической кнопки для подтверждения):
/system/device-mode set container=yes fetch=yes scheduler=yes
Далее, если роутер был сброшен на заводские настройки, сделаем несколько изменений: выключаем IPv6, настраиваем docker registry и NTP:
/ipv6/settings set disable-ipv6=yes /container/config set registry-url=https://registry-1.docker.io tmpdir=/tmp/containers /system/ntp/client set enabled=yes /system/ntp/client/servers add address=0.pool.ntp.org /system/ntp/client/servers add address=1.pool.ntp.org /system/clock set time-zone-name=Europe/Moscow
Создаём bridge для контейнеров и назначаем ему адрес:
/interface/bridge add name=bridge-vpn /ip/address add address=192.168.100.1/24 interface=bridge-vpn network=192.168.100.0
Создаём таблицу маршрутизации vpn‑routes и добавляем в неё дефолтный fallback‑маршрут (через основной bridge, то есть без VPN):
/routing/table add name=vpn-routes fib /ip/route add dst-address=0.0.0.0/0 gateway=bridge routing-table=vpn-routes distance=3 comment="fallback (no VPN)"
AmneziaWG
Используем встроенный в RouterOS WireGuard‑клиент и контейнер‑прокси amneziawg‑proxy‑mikrotik.
Для начала, идем на VPN‑сервер, и с помощью команды /root/awg/manage_amneziawg.sh add создаем пользователя myuser. Его конфиг /root/awg/myuser.conf выглядит примерно так (если вместо конфига есть только ссылка vpn://, см примечание ниже).
[Interface] PrivateKey = saJWFFgzWdeab/3FVzs8P951iGyjwIc6NRlEcg4FoZE= Address = 10.9.9.3/32 DNS = 1.1.1.1, 1.0.0.1 MTU = 1280 Jc = 5 Jmin = 45 Jmax = 121 S1 = 133 S2 = 14 S3 = 22 S4 = 22 H1 = 225777619-532542858 H2 = 542401779-934387227 H3 = 937560472-1145735059 H4 = 1234403339-2442395847 I1 = <r 140> [Peer] PublicKey = 3xswhgno4nk892nHQfsY1yFl5/ECQGAbyRYOgAhzxmA= Endpoint = example.com:12345 AllowedIPs = 0.0.0.0/0 PersistentKeepalive = 32
Возвращаемся на микротик. Вначале, настраиваем сеть контейнера:
/interface/veth add name=veth-awg address=192.168.100.3/24 gateway=192.168.100.1 /interface/bridge/port add bridge=bridge-vpn interface=veth-awg
Создаём WireGuard‑интерфейс (берем значения PrivateKey, MTU и Address из конфига) и добавим правило NAT:
/interface/wireguard add name=wg-awg-proxy mtu=1280 listen-port=12429 private-key="saJWFFgzWdeab/3FVzs8P951iGyjwIc6NRlEcg4FoZE=" /ip/address add address=10.9.9.3/30 interface=wg-awg-proxy /ip/firewall/nat add chain=srcnat action=masquerade out-interface=wg-awg-proxy dst-address-list=!LAN comment=awg
Настраиваем контейнер‑прокси, значения подставляем из конфига (кроме AWG_LISTEN — это константа):
/container/envs add list=AWG key=AWG_REMOTE value="example.com:12345" add list=AWG key=AWG_SERVER_PUB value="3xswhgno4nk892nHQfsY1yFl5/ECQGAbyRYOgAhzxmA=" add list=AWG key=AWG_LISTEN value=":51820" add list=AWG key=AWG_JC value=5 add list=AWG key=AWG_JMIN value=45 add list=AWG key=AWG_JMAX value=121 add list=AWG key=AWG_S1 value=133 add list=AWG key=AWG_S2 value=14 add list=AWG key=AWG_S3 value=22 add list=AWG key=AWG_S4 value=22 add list=AWG key=AWG_H1 value=225777619-532542858 add list=AWG key=AWG_H2 value=542401779-934387227 add list=AWG key=AWG_H3 value=937560472-1145735059 add list=AWG key=AWG_H4 value=1234403339-2442395847 add list=AWG key=AWG_I1 value="<r 140>"
Далее, нужно передать контейнеру публичный ключ WireGuard‑интерфейса роутера. Смотрим его:
/interface/wireguard print
Из вывода команды берём значение поля public-key у интерфейса wg‑awg‑proxy и добавляем еще одну переменную:
/container/envs add list=AWG key=AWG_CLIENT_PUB value="<PUBLIC_KEY_ИНТЕРФЕЙСА>"
Добавляем WireGuard peer и подставляем PublicKey из конфига (выше мы указали, что контейнер будет слушать на 192.168.100.3:51820):
/interface/wireguard/peers add interface=wg-awg-proxy \ endpoint-address=192.168.100.3 endpoint-port=51820 \ allowed-address=0.0.0.0/0 \ persistent-keepalive=25s \ public-key="3xswhgno4nk892nHQfsY1yFl5/ECQGAbyRYOgAhzxmA="
Важно: Если в конфиге клиента есть параметр PresharedKey, его также добавляем в команду выше как еще один параметр
preshared-key="<PresharedKey_value>"
Создаём и запускаем контейнер:
/container add name=amneziawg-proxy-mikrotik:latest-c \ remote-image=wiktorbgu/amneziawg-proxy-mikrotik:latest-c \ interface=veth-awg envlists=AWG \ root-dir=/containers/awg layer-dir="" \ dns=1.1.1.1,8.8.8.8 \ start-on-boot=yes comment=awg /container start [find comment~"awg"]
Добавляем маршрут AWG в таблицу vpn‑routes:
/ip/route add dst-address=0.0.0.0/0 gateway=wg-awg-proxy routing-table=vpn-routes \ distance=1 comment="awg (controlled by netwatch)"
Убеждаемся в отсутствии ошибок в логах контейнера. Доступа по VPN из локальной сети у нас пока нет, мы лишь подняли соединение. Пора переходить ко второму клиенту.
Что делать, если нет конфига, а есть только ссылка «vpn://»?
Такая ссылка содержит параметры подключения в виде URL‑safe base64 + zlib. Распаковать можно командой:
echo -n 'vpn://AAAJV...' | cut -c 7- | tr -- '-_' '+/' | base64 -d | tail -c +5 | openssl zlib -d | jq -r '.containers.[0].awg.last_config' | jq
Параметры те же, что и в конфиге, заполняются аналогично.
Альтернатива: официальный клиент amneziawg‑go
Существует, также, официальный Go‑клиент: amneziawg‑go. Docker‑образ для Mikrotik здесь: wiktorbgu/amneziawg‑mikrotik.
Плюсы:
конфигурируется сразу файлом, а не переменными окружения
не нужно настраивать WireGuard‑клиент роутера.
Минусы:
образ весит ~10 МБ против ~100 КБ у варианта выше (критично для роутера с маленьким диском)
нужно вручную добавлять в файл конфига
PostUp/PostDownкоманды для настройки NAT.также, у меня не получилось заставить его пропускать ICMP через туннель, что ломает мониторинг через netwatch.
Установка выглядит примерно следующим образом. Конфиг Amnezia (с добавленными правилами NAT в виде команд iptables в PostUp/PostDown в секции [Interface]) кладём в каталог /awg_conf роутера, и далее выполняем:
/interface/veth add name=veth-awg address=192.168.100.3/24 gateway=192.168.100.1 /interface/bridge/port add bridge=bridge-vpn interface=veth-awg /container/mounts add list=AWG src=/awg_conf dst=/etc/amnezia/amneziawg mode=ro /container add comment="awg" remote-image=wiktorbgu/amneziawg-mikrotik \ interface=veth-awg mountlists=AWG dns=8.8.8.8,1.1.1.1 \ root-dir=/containers/awg layer-dir="" logging=no start-on-boot=yes name=amneziawg-mikrotik /container start [find comment~"awg"]
Настройка VLESS+Reality
Здесь, также, используем sing‑box.
Но есть нюанс: официальный Docker‑образ нам не подходит, так как не влезет на диск роутера, поэтому берём компактную сборку, например falconerity/sing‑box‑mikrotik (~32 МБ).
Создаём виртуальный интерфейс и включаем его в bridge‑vpn:
/interface/veth add name=veth-vless address=192.168.100.4/24 gateway=192.168.100.1 /interface/bridge/port add bridge=bridge-vpn interface=veth-vless
Создаём конфиг клиента client.json. Берём с нашего VPN сервера следующие значения:
PublicKey из вывода команды
sing-box generate reality-keypairвыше. Соответствующее ему значение PrivateKey мы уже указали в серверном конфигеUUID пользователя
short_id
имя сайта
{ "log": { "level": "info" }, "inbounds": [ { "type": "tun", "interface_name": "tun0", "address": ["10.10.0.1/30"], "auto_route": true, "mtu": 1460, "endpoint_independent_nat": false, "strict_route": false } ], "outbounds": [ { "type": "vless", "server": "example.com", "server_port": 443, "uuid": "bb5b0d3d-931f-44cb-879b-e630c68dd2ec", "flow": "xtls-rprx-vision", "tls": { "enabled": true, "server_name": "google.com", "utls": { "enabled": true, "fingerprint": "firefox" }, "reality": { "enabled": true, "public_key": "-jCoH39J4jgVwWt4ngHRpBugblZBziQ6LVBg21B1umM", "short_id": "2b1f1c1475f38e06" } }, "multiplex": { "enabled": true } } ], "dns": { "strategy": "ipv4_only" }, "route": { "auto_detect_interface": true } }
Подробнее про настройки
Единственный inbound здесь — это TUN‑интерфейс
tun0с адресом10.10.0.1/30, который поднимает sing‑box при старте.
auto_routeозначает, что sing‑box сам настраивает таблицу маршрутизации внутри контейнера, делаяtun0дефолтным для всего входящего трафика (в противном случае это нужно настраивать вручную).mtuустанавливает MTU для IP пакетов (см раздел «MTU и MSS» ниже).fingerprintзадаёт имитируемый браузер при TLS‑handshake.multiplexупаковывает несколько потоков данных в одно TCP‑соединение.auto_detect_interfaceявно указывает использовать выходной интерфейс для внешнего трафика, предотвращая петли маршрутизации.
Подробнее: sing‑box docs
Создаём директорию /vless_conf на роутере и заливаем туда client.json через приложение Winbox или веб‑интерфейс. Для последующего изменения конфигурации достаточно будет перезалить файл конфига и перезапустить контейнер.
Создаём mount и контейнер и запускаем его:
/container/mounts add list=VLESS src=/vless_conf dst=/etc/sing-box mode=ro /container add name=vless-client \ remote-image=falconerity/sing-box-mikrotik:latest \ interface=veth-vless mountlists=VLESS \ root-dir=/containers/vless layer-dir="" \ dns=1.1.1.1,8.8.8.8 shm-size=128.0MiB \ start-on-boot=yes comment=vless /container start [find comment~"vless"]
Добавляем маршруты:
/ip/route add dst-address=0.0.0.0/0 gateway=192.168.100.4 routing-table=vpn-routes \ distance=2 comment="vless (controlled by netwatch)"
Убеждаемся в отсутствии ошибок в логах контейнера. Доступа по VPN из локальной сети у нас пока нет, мы лишь подняли соединение. Переходим к настройке резервирования.
Что делать, если нет конфига, а есть только ссылка «vless://»?
Формат ссылки:
vless://UUID@ADDRESS:PORT?param1=value1&...#SERVER_NAME
UUIDпишем в поле конфигаoutbounds.uuid, ADDRESSв полеoutbounds.server, PORTв полеoutbounds.server_port.
URL‑параметр |
Поле в конфиге клиента |
Описание |
|
- |
Для VLESS+Reality всегда равно «reality» |
|
|
Имя сайта‑маскировки |
|
|
Имитируемый браузер |
|
|
Публичный ключ |
|
|
Short ID |
|
|
VLESS flow, обычно равно «xtls‑rprx‑vision» |
|
|
Тип транспорта (в нашем случае «tcp») |
Включение VPN и настройка резервирования
Создаём правила в таблице mangle. Первое пропускает трафик внутри LAN без перенаправления в VPN (без него потеряем доступ к самому роутеру), второе направляет весь исходящий трафик через VPN (через таблицу vpn‑routes):
/ip/firewall/mangle add chain=prerouting action=accept dst-address-list=LAN add chain=prerouting action=mark-routing new-routing-mark=vpn-routes \ passthrough=no src-address-list=LAN \ comment="Put all traffic to VPN" disabled=no
Если на предыдущих шагах все сделано правильно и в логах контейнеров нет ошибок, то доступ по VPN из LAN уже должен появиться.
Как работает резервирование
Резервирование работает через включение‑отключение маршрутов в таблице vpn‑routes. Трафик идёт по включенному маршруту с наименьшим distance. При падении канала маршрут выключается и трафик начинает идти по другому маршруту с большим distance.
Однако, микротик не может автоматически определить, что VPN упал и маршрут более недоступен, поэтому нам понадобится механизм мониторинга состояния канала.
Часто для этого у маршрута задают check‑gateway: если адрес gateway становится недоступен, маршрут автоматически выключается. Минус — просто, но не гибко, так как проверка ограничивается только ping и arp probe (что не будет работать для VLESS в нашем случае) и нельзя повесить кастомную команду на срабатывание проверки. Также, всё усложняется, если адрес шлюза отличается от адреса, который проверяем: тогда настраивают recursive routing — это отдельная тема.
Вместо этого мы будем использовать netwatch — список задач, периодически проверяющих доступность хостов. В зависимости от результата, задача переходит в состояние up или down, выполняя соответствующий скрипт. В netwatch, также, больше выбор типов проверок, чем в check‑gateway.
Для мониторинга каналов VPN добавим по одной задаче netwatch на каждый. Для Amnezia достаточно пинговать IP сервера в туннеле. В случае с VLESS, sing‑box не поддерживает ICMP через VLESS, поэтому будем проверять доступность через открытие TCP соединения на сервер (по умолчанию 80 порт).
/tool/netwatch add host=10.9.9.1 interval=30s type=simple startup-delay=1m30s \ comment="awg is operable" \ up-script="/ip route enable [find where comment~\"awg\" and routing-table=\"vpn-routes\"]" \ down-script="/ip route disable [find where comment~\"awg\" and routing-table=\"vpn-routes\"]" add host=10.10.0.2 interval=30s type=tcp-conn startup-delay=1m30s \ comment="vless is operable" \ up-script="/ip route enable [find where comment~\"vless\" and routing-table=\"vpn-routes\"]" \ down-script="/ip route disable [find where comment~\"vless\" and routing-table=\"vpn-routes\"]" /ip route add comment="Router requests to Amnezia" disabled=no distance=1 dst-address=\ 10.9.9.0/24 gateway=wg-awg-proxy routing-table=main scope=30 \ target-scope=10 add comment="Router requests to VLESS" disabled=no distance=1 dst-address=\ 10.10.0.0/24 gateway=192.168.100.4 routing-table=main scope=30 \ target-scope=10
Чтобы протестировать резервирование, можно остановить один из VPN на сервере, и в течение ~30 секунд соответствующий маршрут в vpn‑routes должен выключится. При восстановлении маршрут включится обратно.
Если соединение очень медленное или постоянно обрывается, отключите правило fasttrack в таблице filter фаервола, если оно есть.
Раздельное туннелирование (split routing)
Чтобы пускать через VPN трафик только до заблокированных ресурсов, а не весь, нужно настроить split routing. VPN клиенты поддерживают split routing самостоятельно, но у нас VPN’ов несколько, поэтому правильнее будет настроить общий.
Здесь мы будем использовать самый простой подход с address‑list: все исходящие соединения на адреса из списка подсетей уходят в таблицу vpn‑routes (и оттуда уже в VPN клиенты), а все остальные идут напрямую. Списки регулярно обновляются скриптом по шедулеру.
Какие есть альтернативы такому подходу?
Первая это BGP. Маршруты будут приходить автоматически без скриптов. Есть много гайдов по настройке BGP на микротике, например: https://habr.com/ru/articles/359268/.
Вторая альтернатива это роутинг по заранее известному списку доменов — dns static list.
В двух словах, работает это так: роутер в ответ на DNS запрос разрешает домен в IP адрес, и, если такой домен есть в dns static list, то IP адрес попадает, также, в указанный в нем address‑list. Далее, все работает как в нашем случае: split routing на основе address‑list, только теперь не подсетей, а IP адресов, разрешенных через DNS.
Минус второго подхода в том, что без явных DNS запросов к роутеру это не работает. Современные браузеры, зачастую, не делают запросы в DNS сервер напрямую, а вместо этого используют свой собственный DNS‑over‑HTTP. Хотя это можно выключить в настройках, с гостевыми устройствами это затруднительно.
Пример настройки роутинга по списку доменов: здесь мы отправляем трафик на домены из dns static list в нашу таблицу vpn‑routes:
/ip dns set allow-remote-requests=yes /ip/dns/forwarders add name="forwarder1" dns-servers="1.1.1.1,8.8.8.8" /ip/firewall/mangle add action=mark-routing chain=output dst-address-list=domains-to-vpn new-routing-mark=vpn-routes passthrough=no
Теперь сами домены, например, youtube.com:
/ip/dns/static add name=youtube.com type=FWD forward-to=forwarder1 address-list=domains-to-vpn match-subdomain=yes
Скрипт обновления списков
У скриптов микротика специфический синтаксис со множеством ограничений. Например, fetch не может скачать больше 64 килобайт за раз, и так далее. В общем, напополам с claude, у меня получилось сделать такой скрипт (требует RouterOS 7.22+, так как там появилась поддержка :continue и :break):
:global fetchWithRetry do={ :local maxretry 3; :local delay 5s; :for retry from=1 to=$maxretry step=1 do={ :if ($light = true) do={ :return [/tool fetch url=$url keep-result=no as-value]; } else={ :return [/tool fetch url=$url http-header-field=$headers output=user as-value]; }; :delay $delay; } on-error={}; :return [:nothing]; } :local maxretry 3; :local delay 120s; # Add here the lists to update in format "name"="url" :local urls { "antifilter-ipsum"="https://antifilter.network/download/ipsum.lst"; "antifilter-subnet"="https://antifilter.network/download/subnet.lst"; "telegram"="https://core.telegram.org/resources/cidr.txt"; "youtube"="https://raw.githubusercontent.com/touhidurrr/iplist-youtube/main/lists/cidr4.txt" }; :local ipRegex "^((25[0-5]|(2[0-4]|[01]?[0-9]?)[0-9])\\.){3}(25[0-5]|(2[0-4]|[01]?[0-9]?)[0-9])(\/(3[0-2]|[0-2]?[0-9])){0,1}\$"; :foreach listname,url in=$urls do={ :log info "Processing list $listname from $url..."; :do { /ip firewall address-list remove [find where list=($listname."-updated")]; } on-error={}; :do { /ip firewall address-list add list=($listname."-updated") address=antifilter.download comment=$listname; } on-error={}; # Get file info without fetching the data :local fileResponse [$fetchWithRetry light=true url=$url headers=""]; :log info $fileResponse; :if ($fileResponse = [:nothing] || $fileResponse->"code" != 200) do={ :log error "Cannot download the IP list. Response: $fileResponse"; :continue }; # Download data by chunks. Max data size the "fetch" tool can keep is 64KiB, see its docs :local filesize ($fileResponse->"total"); :local chunksize 64000; :local start 0; :local end ($chunksize - 1); :local chunks ($filesize / ($chunksize / 1024)); :local lastchunk ($filesize % ($chunksize / 1024)); :if ($lastchunk > 0) do={ :set $chunks ($chunks + 1); }; :local data ""; :local prevdata ""; :set $counter 0; :for chunk from=1 to=$chunks step=1 do={ # Fetch the data chunk. If some bytes remained unparsed after the previous chunk, insert it before the current data :local requestHeaders "Range: bytes=$start-$end"; :local chunkResponse [$fetchWithRetry light=false url=$url headers=$requestHeaders]; :if ($chunkResponse = [:nothing] || $fileResponse->"code" != 200) do={ :log error "Cannot download the file chunk. Response: $chunkResponse"; :set $counter 0; :break }; :set $data ($chunkResponse->"data"); :set $data ($prevdata . $data); :while ([:len [:find $data "\n"]] != 0) do={ :local line [:pick $data 0 [:find $data "\n"]]; # Add CIDR addresses into the temporary address-list, discard everything unmatched :if ( $line ~ $ipRegex ) do={ :do { /ip firewall address-list add list=($listname."-updated") address=$line; :set $counter ($counter + 1); } on-error={}; }; :set $data [:pick $data ([:find $data "\n"]+1) [:len $data]]; }; :set $prevdata $data; :set $start ($start + $chunksize); :set $end ($end + $chunksize); }; # If IPs were downloaded, move addresses from the temporary address-list into the main one :if ($counter > 0) do={ :do { /ip firewall address-list remove [find where list=$listname]; } on-error={}; :do { :foreach address in=[/ip firewall address-list find list=($listname."-updated")] do={ :do { /ip firewall address-list set list=$listname $address; } on-error={}; }; } on-error={}; }; :log info "Processing $listname finished, $counter rules has been added"; };
Что здесь происходит?
Вначале задаётся мапа имя: url. Каждый url скачивается чанками по 64 КБ, IPv4 адреса парсятся и добавляются во временный address‑list. После успешного завершения временный список заменяет основной. При ошибке обработка текущего url прерывается, прежний address‑list остается нетронутым и скрипт переходит к следующему url.
Идем в веб‑интерфейс или WinBox в System→Scripts и добавляем скрипт с содержимым выше.
Далее, создаём правила firewall для split‑routing, по одному на каждый address‑list и выключаем правило «весь трафик в VPN»:
/ip/firewall/mangle add chain=prerouting action=mark-routing new-routing-mark=vpn-routes passthrough=no \ src-address-list=LAN dst-address-list=antifilter-ipsum comment=split-routing add chain=prerouting action=mark-routing new-routing-mark=vpn-routes passthrough=no \ src-address-list=LAN dst-address-list=antifilter-subnet comment=split-routing add chain=prerouting action=mark-routing new-routing-mark=vpn-routes passthrough=no \ src-address-list=LAN dst-address-list=telegram comment=split-routing add chain=prerouting action=mark-routing new-routing-mark=vpn-routes passthrough=no \ src-address-list=LAN dst-address-list=youtube comment=split-routing /ip/firewall/mangle [find comment="Put all traffic to VPN"] disabled=yes
Добавляем задание в scheduler. Хотим обновлять списки, скажем, раз в 2 дня:
/system/scheduler add comment="Update address lists" interval=2d name=banned-ips-updater on-event="/system script run \"update-banned-ips\""
DNS‑over‑HTTPS (DoH)
Некоторые интернет‑провайдеры в ответ на DNS запрос могут присылать подменный IP адрес вместо реального, указывающий, например, на страницу с сообщением о блокировке. Это один из самых простых способов блокировки сайтов.
Для борьбы с этим настроим DNS‑over‑HTTP (DoH). К сожалению, на сегодняшний момент, RouterOS поддерживает указание только одного основного DoH сервера.
/tool fetch url="https://pki.goog/roots.pem" /certificate import file-name=roots.pem passphrase="" /ip/dns set allow-remote-requests=yes \ use-doh-server=https://dns.google/dns-query \ verify-doh-cert=yes \ servers=8.8.8.8,1.1.1.1
Выключить получение DNS от провайдера (если используется DHCP):
/ip/dhcp-client set use-peer-dns=no [find]
Опционально: LED‑индикация состояния каналов
Mikrotik hap ax2 имеет два управляемых LED на корпусе: USR (зелёный) и PoE out (красный). Используем их для визуальной индикации состояния туннелей. Это может быть полезно при визуальной диагностике «по телефону» (помним из предисловия, что к роутеру может пропасть удаленный доступ).
Объявляем объекты:
/system/leds remove [find] /system/leds add leds=user-led type=off /system/leds add leds=poe-led type=off
Пересоздаём наши netwatch‑задания, добавив управление LED:
/tool/netwatch remove [find comment~"awg" or comment~"vless"] /tool/netwatch add host=10.9.9.1 interval=30s type=simple startup-delay=1m30s \ comment="awg is operable" \ up-script="/ip route enable [find where comment~\"awg\" and routing-table=\"vpn-routes\"]\ \n/system/leds set [find leds=\"user-led\"] type=on" \ down-script="/ip route disable [find where comment~\"awg\" and routing-table=\"vpn-routes\"]\ \n/system/leds set [find leds=\"user-led\"] type=off" /tool/netwatch add host=10.10.0.2 interval=30s type=tcp-conn startup-delay=1m30s \ comment="vless is operable" \ up-script="/ip route enable [find where comment~\"vless\" and routing-table=\"vpn-routes\"]\ \n/system/leds set [find leds=\"poe-led\"] type=on" \ down-script="/ip route disable [find where comment~\"vless\" and routing-table=\"vpn-routes\"]\ \n/system/leds set [find leds=\"poe-led\"] type=off"
После настройки зелёная лампочка показывает состояние AmneziaWG, красная — состояние VLESS.
Опционально: физическая кнопка включения/выключения VPN
На Mikrotik hap ax2 есть кнопка MODE, на которую можно повесить скрипт. Сделаем из неё kill‑switch для VPN. Нажатие превращает роутер в обычный роутер без VPN, повторное включает VPN обратно. Может быть полезно, если что‑то пошло не так, а удалённый доступ пропал.
/system/routerboard/mode-button set enabled=yes \ on-event=":if ([len [/tool/netwatch find where disabled=no]] > 0) do={\ \n /tool/netwatch disable [find comment~\"awg\" or comment~\"vless\"]\ \n /system/leds set [find] type=off\ \n /ip route disable [find where comment~\"vless\" and routing-table=\"vpn-routes\"]\ \n /ip route disable [find where comment~\"awg\" and routing-table=\"vpn-routes\"]\ \n /container stop [find comment~\"awg\" or comment~\"vless\"]\ \n /container set [find comment~\"awg\" or comment~\"vless\"] start-on-boot=no\ \n} else={\ \n /container start [find comment~\"awg\" or comment~\"vless\"]\ \n /container set [find comment~\"awg\" or comment~\"vless\"] start-on-boot=yes\ \n /tool/netwatch enable [find comment~\"awg\" or comment~\"vless\"]\ \n}"
Оптимизации
Второй IP для VPN
В последние месяцы появляются сообщения о том, что многие российские приложения пытаются выяснить, используете ли вы VPN и его внешний адрес, используя публичные сервисы, такие, как ipify.org. Далее эта информация, возможно, уходит в РКН. Если у VPN один IP и адрес подключения совпадает с выходным, его блокировка ломает всё сразу.
Самое простое решение этой проблемы ― купить 2-й IP адрес. Тогда подключение идёт на один IP (например, через интерфейс eth0), а наружу трафик будет выходить через другой (eth1). Блокировка выходного IP не влияет на доступность туннеля. Правда, это не очень поможет, если РКН заблокирует всю подсеть хостера.
Примерная последовательность после добавления второго IP в личном кабинете хостера такая:
Настроить дефолтный маршрут через
eth1(в панели хостера или в конфигурации сети VPS).-
Заблокировать входящие соединения на
eth1:ufw deny in on eth1
AmneziaWG
Редактируем /etc/amnezia/amneziawg/awg0.conf, находим в PostUp/PostDown подобное правило NAT iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE и заменяем eth0 на eth1. Применяем изменения:
systemctl restart awg-quick@awg0
sing‑box
Указываем интерфейс через параметр конфига bind_interface в секции inbounds:
"inbounds": [ { "type": "vless", "listen": "0.0.0.0", "listen_port": 443, "bind_interface": "eth0", ... } ]
Применяем изменения:
systemctl restart sing-box
MTU и MSS
На некоторых провайдерах VPN‑соединения могут тормозить, особенно на мобильных. Одна из частых причин ― это неоптимальные значения MTU/MSS.
В чем проблема?
Если по пути трафика встречается канал, где значение MTU по какой‑то причине меньше, чем у пакета, и у пакета стоит флаг DF, пакет будет отброшен, а промежуточный узел отправит обратно ICMP сообщение с требованием фрагментации. Проблема в том, фаерволлы операторов часто блокируют такой ICMP‑трафик (PMTUD blackhole). В результате, до отправителя это ICMP сообщение не доходит, и соединение висит.
Внешне это проявляется как зависание передачи больших объемов данных, но при этом короткие запросы проходят успешно.
Как чинить: уменьшаем MTU и MSS до тех пор, пока не станет лучше. Типовые варианты: MTU=1460/MSS=1420, MTU=1420/MSS=1380, MTU=1360/MSS=1320, MTU=1280/MSS=1240 и так далее. Для мобильных можно сразу брать значения поменьше.
AmneziaWG
MTU задаётся параметром MTU в секции [Interface] конфига сервера. На клиенте задается в параметрах интерфейса WireGuard. MSS — через правила iptables в PostUp/PostDown в конфиге. Подробнее в FAQ проекта amnezia‑installer.
sing‑box
MTU задаётся параметром mtu в inbounds как на сервере, так и в клиентском client.json. MSS на стороне Mikrotik:
/ip/firewall/mangle add chain=forward action=change-mss \ new-mss=1380 passthrough=yes protocol=tcp tcp-flags=syn \ out-interface=veth-vless comment="MSS clamp for VLESS"
Дальнейшие оптимизации
Выше ― далеко не полный список того, как можно оптимизировать архитектуру.
Например, если нужно более удобное управление пользователями или VPN профилями, можно установить веб‑панель на сервере (3x‑ui, s‑ui, etc.). Также, sing‑box умеет работать с другими протоколами для обхода блокировок и кастомный роутинг: можно заворачивать разные ресурсы в разные протоколы.
Также, можно потюнить параметры, влияющие на «пробиваемость» DPI, настроить мониторинг доступности через внешний сервис, а также, для максимальной отказоустойчивости, разнести VPN на два физически разных сервера в разных датацентрах/юрисдикциях, сделать резервирование каналов доступа в интернет от разных провайдеров, настроить каскад серверов и так далее
Можно начать с FAQ по настройке VLESS и Amnezia.
Пространства для дальнейшей оптимизации достаточно, но это уже за рамками статьи.
Заключение
На выходе у нас получилась минимальная рабочая конфигурация:
два принципиально разных туннеля (AmneziaWG и VLESS+Reality) с автоматическим переключением при падении одного из них;
раздельная маршрутизация (split‑routing) с автоматическим обновлением списков;
DNS‑over‑HTTP для защиты DNS‑спуфинга;
VPN клиент не требует обслуживания в штатных ситуациях;
в качестве бонуса: светодиодная индикация состояния каналов и физический kill switch для VPN.
Схема, описанная здесь это компромисс между надёжностью, стоимостью и трудозатратами на настройку. Для домашнего использования с небольшим кругом пользователей она вполне оправдана.
Комментарии (11)

KotPoliglot
25.08.2026 15:12Годно, у меня пока только амнезия, при разрыве откат на провайдера. О резерве только думал, а вот и ман подъехал.
Спасибо)

lexor64
25.08.2026 15:12Вместо контейнера под амнезию можно использовать специальный скрипт, который работает с использованием встроенного клиента WG. Или пойти дальше и развернуть контейнер с mihomo, там внутри и vless, и amnezia. Есть несколько проектов с mihomo специально под микротик: можно и просто весь трафик заворачивать туда и по доменам рулить самим mihomo, а можно сделать минимальный конфиг с выпуском всего трафика в VPN, а какой трафик и на какие адреса - настраивать на микроте.

bbasil
25.08.2026 15:12А попроще разве ничего нет ?))
Начните изучение с ip proto, там много интересных открытий будет )

ZHAXARI
25.08.2026 15:12Mikrotik hap ax2 (C52iG-5HaxD2HaxD) — бюджетное (хоть не самое дешевое) решение с диском 128 МБ (свободно ~80 МБ на старте, но нам хватит) и RAM 1ГБ.
Если дийщик можно расширить, до 1Гб. Перепаять можно ПЗУ, потом только заново прошивку накатить через утилиту микротика надо.

lexor64
25.08.2026 15:12Можно к нему просто USB-флешку подключить, и все, что связано с контейнерами, держать там.Ошибся, у ax2 нет же USB, перепутал с ax3. Но можно подключить сетевой каталог, хотя бы sshfs
JDJ
ARM32 поддерживают контейнеры?
KotPoliglot
Да!
Лет десять назад не поверил бы что буду крутить докер на роутере)
JDJ
можете назвать модель?
KotPoliglot
hAP ac2/3
denisemenov
Ac2? Там же места не хватает примерно ни на что? У меня сетка с cap'ами и после установки wireless осталось пару килобайт.
Domestomag
ac2 можно перепрошить в openwrt и воткнуть флешку для расширения памяти
.. а потом просто настроить там ssh доступ, запустить ии агента и сказать ему что бы связал роутер с сервером по 2+ разным впн каналам и можно откинутся на спинку кресла.