Как повысить надежность 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‑параметр

Поле в конфиге клиента

Описание

security

-

Для VLESS+Reality всегда равно «reality»

sni

outbounds.tls.server_name

Имя сайта‑маскировки

fp

outbounds.tls.utls.fingerprint

Имитируемый браузер

pbk

outbounds.tls.reality.public_key

Публичный ключ

sid

outbounds.tls.reality.short_id

Short ID

flow

outbounds.flow

VLESS flow, обычно равно «xtls‑rprx‑vision»

type

outbounds.transport.type

Тип транспорта (в нашем случае «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 в личном кабинете хостера такая:

  1. Настроить дефолтный маршрут через eth1 (в панели хостера или в конфигурации сети VPS).

  2. Заблокировать входящие соединения на 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)


  1. JDJ
    25.08.2026 15:12

    ARM32 поддерживают контейнеры?


    1. KotPoliglot
      25.08.2026 15:12

      Да!

      Лет десять назад не поверил бы что буду крутить докер на роутере)


      1. JDJ
        25.08.2026 15:12

        можете назвать модель?


        1. KotPoliglot
          25.08.2026 15:12

          hAP ac2/3


          1. denisemenov
            25.08.2026 15:12

            Ac2? Там же места не хватает примерно ни на что? У меня сетка с cap'ами и после установки wireless осталось пару килобайт.


            1. Domestomag
              25.08.2026 15:12

              ac2 можно перепрошить в openwrt и воткнуть флешку для расширения памяти

              .. а потом просто настроить там ssh доступ, запустить ии агента и сказать ему что бы связал роутер с сервером по 2+ разным впн каналам и можно откинутся на спинку кресла.


  1. KotPoliglot
    25.08.2026 15:12

    Годно, у меня пока только амнезия, при разрыве откат на провайдера. О резерве только думал, а вот и ман подъехал.

    Спасибо)


  1. lexor64
    25.08.2026 15:12

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


  1. bbasil
    25.08.2026 15:12

    А попроще разве ничего нет ?))

    Начните изучение с ip proto, там много интересных открытий будет )


  1. ZHAXARI
    25.08.2026 15:12

    Mikrotik hap ax2 (C52iG-5HaxD2HaxD) — бюджетное (хоть не самое дешевое) решение с диском 128 МБ (свободно ~80 МБ на старте, но нам хватит) и RAM 1ГБ.

    Если дийщик можно расширить, до 1Гб. Перепаять можно ПЗУ, потом только заново прошивку накатить через утилиту микротика надо.


    1. lexor64
      25.08.2026 15:12

      Можно к нему просто USB-флешку подключить, и все, что связано с контейнерами, держать там.

      Ошибся, у ax2 нет же USB, перепутал с ax3. Но можно подключить сетевой каталог, хотя бы sshfs