Маршрутизация в sing-box: большой гайд по слоям — tun, sniff, DNS, route.rules и rule-set

Поднять сервер с Reality и подключиться к нему — задача одного вечера. Настроить так, чтобы банк открывался напрямую, Netflix шёл через прокси, реклама резалась на подлёте, а DNS не рассказывал провайдеру, куда вы ходите — задача другого порядка. Именно здесь ломается больше всего конфигов, и именно про это в подписках от продавцов обычно ничего нет: там просто "final": "proxy" и всё.

Этот гайд разбирает клиентскую часть sing-box по слоям — в том порядке, в котором пакет реально проходит через программу. Актуально для стабильной ветки 1.13.x (последний релиз на момент написания — v1.13.16 от 3 августа 2026; ветка 1.14 пока в бете). Всё, что ниже, взято из официальной документации sing-box.sagernet.org, а не из чужих статей.

Если вы ещё не разбирались, как устроен сам транспорт, начните с соседней темы — как работает Reality (XTLS) и почему он обходит DPI.


Слой 0. Сначала решите стратегию, потом пишите JSON

Прежде чем открывать редактор, надо ответить на один вопрос: что происходит с трафиком, который не подошёл ни под одно правило? От ответа зависит вообще всё остальное.

Important:

Ключевой принцип: правила описывают исключения, а route.final описывает умолчание. Большинство сломанных конфигов — это попытка описать правилами всё сразу, вместо того чтобы выбрать правильное умолчание и добавить к нему десяток исключений.

Для российского пользователя в 2026-м разумный дефолт — белый список: final указывает на прокси, а direct получают только явно перечисленные российские адреса и домены. Причина простая: список блокировок растёт быстрее, чем вы успеваете его править, а список того, что должно идти напрямую (банки, госуслуги, локальные сервисы, серый IP-диапазон), почти не меняется.


Слой 1. Приём трафика: tun и зачем нужен sniff

Режим tun создаёт виртуальный сетевой интерфейс и забирает на себя весь трафик системы — не только тот, который приложение согласилось отдать в прокси.

{
  "inbounds": [
    {
      "type": "tun",
      "tag": "tun-in",
      "address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"],
      "auto_route": true,
      "strict_route": true,
      "stack": "mixed"
    }
  ]
}

Что тут важно:

Поле Значение Зачем
address список префиксов (с 1.10.0) Раньше были раздельные inet4_address / inet6_address — в старых гайдах они ещё встречаются
auto_route true Прописывает маршрут по умолчанию в туннель
strict_route true Заставляет систему уважать эти маршруты; без него часть трафика утекает мимо
stack system / gvisor / mixed mixed — компромисс: TCP через system, UDP через gVisor
auto_redirect Linux, с 1.10.0 Ускоряет маршрутизацию через nftables вместо tun-стека
Note:

На Linux с auto_redirect появляется отдельное действие bypass (добавлено в 1.13.0) — оно возвращает соединение в обычный сетевой стек мимо туннеля. Полезно для трафика, который должен уходить в локальную сеть без накладных расходов tun.

Почему без sniff маршрутизация по доменам не работает

Когда трафик приходит из tun, sing-box видит только адрес назначения — IP и порт. Домена там нет: приложение уже отрезолвило его само. А правила вроде «youtube.com — через прокси» оперируют доменами.

Решает это действие sniff: sing-box заглядывает в начало соединения, вытаскивает SNI из TLS ClientHello, Host из HTTP или ALPN из QUIC — и дальше маршрутизирует уже по домену.

{
  "route": {
    "rules": [
      { "action": "sniff", "timeout": "300ms" },
      { "protocol": "dns", "action": "hijack-dns" }
    ]
  }
}
Warning:

Если вы переносите конфиг из версии до 1.11, у вас sniff скорее всего стоит полем внутри inbound ("sniff": true). Это устаревший синтаксис: с 1.11.0 сниффинг стал действием правила. Аналогично исчезли специальные outbound’ы block и dns — они превратились в "action": "reject" и "action": "hijack-dns".


Слой 2. DNS — место, где чаще всего всё и ломается

Симптом знакомый: сайт вроде бы должен идти через прокси, но открывается «российская» версия, или не открывается вовсе. Почти всегда причина в том, что имя резолвилось не тем сервером.

С версии 1.12.0 формат DNS-серверов переписан. Старое поле address со схемой в URL заменено на явные type + server:

Было (до 1.12) Стало (1.12+)
"address": "local" "type": "local"
"address": "1.1.1.1" "type": "udp", "server": "1.1.1.1"
"address": "tls://1.1.1.1" "type": "tls", "server": "1.1.1.1"
"address": "https://1.1.1.1/dns-query" "type": "https", "server": "1.1.1.1"
"address": "quic://1.1.1.1" "type": "quic", "server": "1.1.1.1"
"address": "fakeip" "type": "fakeip" с inet4_range / inet6_range

Рабочая раскладка выглядит так:

{
  "dns": {
    "servers": [
      {
        "tag": "dns-proxy",
        "type": "https",
        "server": "1.1.1.1",
        "detour": "proxy"
      },
      {
        "tag": "dns-local",
        "type": "udp",
        "server": "192.168.1.1",
        "detour": "direct"
      }
    ],
    "rules": [
      { "rule_set": ["geosite-category-ru"], "server": "dns-local" }
    ],
    "final": "dns-proxy",
    "strategy": "prefer_ipv4"
  }
}

Смысл: российские домены резолвит роутер (иначе вы получите чужой CDN-узел и потеряете скорость), всё остальное уезжает в DoH через сам прокси — благодаря detour. Провайдер в этом случае видит только зашифрованное соединение с вашим сервером.

Security:

detour у DNS-сервера — это и есть защита от DNS-утечек. Без него запросы уходят открытым UDP с вашего адреса, и весь список посещённых доменов остаётся у провайдера, даже если сам трафик потом идёт через туннель. Проверять результат стоит не «на глаз», а на dnsleaktest-подобном сервисе и параллельно tcpdump port 53 на шлюзе.

Error:

Типичная ловушка: указать в DoH-сервере доменное имя ("server": "dns.example.com") и отправить его в detour: "proxy". Чтобы подключиться к прокси, нужно отрезолвить его адрес; чтобы отрезолвить — нужен DNS, который ходит через прокси. Круг замкнулся, и sing-box не стартует корректно. Решения два: указывать DoH-сервер по IP, либо задать domain_resolver (появился в 1.12.0) с указанием отдельного bootstrap-сервера.

Ещё одна тонкость, которую полезно знать: resolve — это тоже действие правила, а не свойство inbound. Оно принудительно резолвит домен на нужном сервере в нужную сторону:

{ "action": "resolve", "server": "dns-local", "strategy": "ipv4_only" }

Пригождается, когда провайдер выдаёт кривой IPv6 или когда правило дальше по цепочке матчит по ip_cidr и ему нужен уже готовый адрес.


Слой 3. route.rules: первое совпадение выигрывает

Это ядро всей конструкции. Правила проверяются сверху вниз, срабатывает первое подошедшее, остальные не рассматриваются. Отсюда главное практическое следствие: порядок правил — часть логики, а не косметика.

Набор полей у правила огромный. Вот те, которые реально используются в клиентских конфигах:

Поле Что матчит
domain, domain_suffix, domain_keyword, domain_regex Домен — точно, по суффиксу, по подстроке, по регулярке
ip_cidr, source_ip_cidr Адрес назначения / источника
ip_is_private, source_ip_is_private Непубличные адреса (серые сети, localhost)
port, port_range, source_port, source_port_range Порты
network tcp, udp, icmp
protocol, client То, что определил сниффер
process_name, process_path, package_name Приложение: имя процесса (desktop) или пакет Android
network_type, network_is_expensive Wi-Fi / сотовая / Ethernet, лимитная сеть
wifi_ssid, wifi_bssid Конкретная Wi-Fi-сеть
clash_mode Текущий режим в Clash API (Rule / Global / Direct)
rule_set Ссылка на внешний список
invert Инвертировать результат правила

Действия (action), которые завершают обработку:

  • route — отправить в outbound (поле outbound; поле outbound прямо в правиле устарело с 1.11.0 в пользу action);
  • reject — оборвать. Есть method: default (TCP RST / ICMP port unreachable), drop (молча выбросить), reply (только для ICMP echo);
  • hijack-dns — увести в DNS-модуль;
  • bypass — вернуть в системный стек (Linux + auto_redirect, с 1.13.0).

И не завершающие: sniff, resolve, route-options. Последнее — это набор тюнинга поверх маршрута: override_address, override_port, udp_timeout, а также tls_fragment и tls_record_fragment (с 1.12.0), которые режут TLS ClientHello на части — иногда этого хватает, чтобы проскочить примитивный DPI без прокси вообще.

Info:

Правила можно комбинировать логически: {"type": "logical", "mode": "and", "rules": [...]}. Режимы — and и or. Классический пример: «домен из списка Х и сеть сотовая» — на мобильном интернете гнать через прокси, на домашнем Wi-Fi не гнать.


Слой 4. rule_set: откуда берутся сами списки

Раньше были поля geoip и geosite с монолитными базами. Начиная с 1.8.0 они заменены на rule-set — отдельные списки, которые подключаются по тегу и обновляются независимо.

Три типа:

  • inline (с 1.10.0) — правила прямо в конфиге, без внешнего файла;
  • local — файл на диске, перечитывается при изменении;
  • remote — скачивается по URL и кешируется.

Официальные сборки лежат в репозиториях SagerNet/sing-geoip и SagerNet/sing-geosite, ветка rule-set:

{
  "route": {
    "rule_set": [
      {
        "tag": "geoip-ru",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-ru.srs",
        "download_detour": "proxy",
        "update_interval": "1d"
      },
      {
        "tag": "geosite-category-ru",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ru.srs",
        "download_detour": "proxy"
      }
    ]
  },
  "experimental": {
    "cache_file": { "enabled": true }
  }
}
Error:

Тег geosite-ru не существует — по этому имени вы получите 404 и молча пустой список. Российская категория в sing-geosite называется geosite-category-ru. Проверить наличие любого списка можно просто открыв URL в браузере: отдаётся файл — тег есть, приходит «404: Not Found» — тега нет. Это первое, что стоит сделать перед тем, как копировать чужой конфиг из чата.

Success:

experimental.cache_file.enabled: true — не опция «на будущее», а обязательное условие для remote rule-set. Без неё списки будут перекачиваться при каждом старте: медленно, а на мобильном интернете ещё и дорого.

Свой список собирается из JSON-исходника командой:

sing-box rule-set compile --output my-direct.srs my-direct.json

Исходник выглядит так:

{
  "version": 3,
  "rules": [
    { "domain_suffix": [".gosuslugi.ru", ".nalog.gov.ru"] }
  ]
}

Поле version — это версия формата, не вашего файла, и она определяет, какие типы правил вообще можно использовать:

version Появилась в Что добавила
1 1.8.0 Исходный формат
2 1.10.0 Оптимизация памяти для domain_suffix
3 1.11.0 Сетевые правила (network_type и родня)
4 1.13.0 Правила по адресам интерфейсов
5 1.14.0 package_name_regex
Warning:

Скомпилировали список с version: 5 — старый клиент его не прочитает. Это частая причина загадочного «правило есть, а не работает» после того, как кто-то в чате поделился свежим .srs. Совместимость идёт только снизу вверх.

Если вы переезжаете на 1.14, учтите: download_detour там объявлен устаревшим, вместо него — общий механизм http_client. На 1.13.x он продолжает работать штатно.


Собираем всё вместе

Минимальный, но осмысленный конфиг «белый список» целиком:

{
  "log": { "level": "info", "timestamp": true },
  "dns": {
    "servers": [
      { "tag": "dns-proxy", "type": "https", "server": "1.1.1.1", "detour": "proxy" },
      { "tag": "dns-local",  "type": "udp",   "server": "192.168.1.1", "detour": "direct" }
    ],
    "rules": [ { "rule_set": ["geosite-category-ru"], "server": "dns-local" } ],
    "final": "dns-proxy"
  },
  "inbounds": [
    { "type": "tun", "tag": "tun-in", "address": ["172.19.0.1/30"],
      "auto_route": true, "strict_route": true, "stack": "mixed" }
  ],
  "outbounds": [
    { "type": "vless", "tag": "proxy", "server": "203.0.113.10", "server_port": 443,
      "uuid": "ВАШ-UUID", "flow": "xtls-rprx-vision",
      "tls": { "enabled": true, "server_name": "www.example.com",
               "utls": { "enabled": true, "fingerprint": "chrome" },
               "reality": { "enabled": true, "public_key": "ВАШ-PUBLIC-KEY", "short_id": "ВАШ-SHORT-ID" } } },
    { "type": "direct", "tag": "direct" }
  ],
  "route": {
    "rules": [
      { "action": "sniff" },
      { "protocol": "dns", "action": "hijack-dns" },
      { "ip_is_private": true, "action": "route", "outbound": "direct" },
      { "rule_set": ["geosite-ads"], "action": "reject", "method": "drop" },
      { "rule_set": ["geosite-category-ru", "geoip-ru"], "action": "route", "outbound": "direct" }
    ],
    "rule_set": [
      { "tag": "geosite-category-ru", "type": "remote", "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ru.srs",
        "download_detour": "proxy" },
      { "tag": "geoip-ru", "type": "remote", "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-ru.srs",
        "download_detour": "proxy" },
      { "tag": "geosite-ads", "type": "remote", "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ads-all.srs",
        "download_detour": "proxy" }
    ],
    "final": "proxy",
    "auto_detect_interface": true
  },
  "experimental": { "cache_file": { "enabled": true } }
}

Порядок правил здесь и есть логика: сначала техника (сниффинг, перехват DNS), потом локальная сеть, потом резка рекламы, потом «своё — напрямую», и только после этого — умолчание в прокси.

Success:

Перед запуском — обязательно два шага:

sing-box check -c config.json      # синтаксис и ссылки на несуществующие теги
sing-box format -c config.json -w  # привести к каноническому виду

check ловит большинство опечаток в тегах — самую частую причину «конфиг вроде правильный, а трафик идёт не туда».


Когда правило не срабатывает: порядок отладки

  1. Проверьте порядок. Правило ниже более общего никогда не выполнится. ip_is_private в конце списка бесполезен.
  2. Проверьте, есть ли вообще домен. Нет действия sniff — нет доменов, и все domain_suffix мертвы.
  3. Поднимите уровень лога до debug и посмотрите, какой outbound выбран для конкретного соединения. Там же видно, отработал ли сниффер.
  4. Проверьте сам списокsing-box rule-set match покажет, попадает ли конкретный домен в .srs, не заставляя вас гадать.
  5. Проверьте, что список скачался. Remote rule-set без cache_file и без доступа к GitHub — это пустой список, который молча ничего не матчит.
Note:

Отдельная категория — приложения, которые вас игнорируют: браузеры с DoH внутри себя, мессенджеры с захардкоженными резолверами, устройства с прошитым 8.8.8.8. Их лечат правилом {"port": 53, "action": "hijack-dns"} плюс блокировкой исходящего DoH — но полностью закрыть эту дыру на клиенте нельзя, только на шлюзе.


Чего этот подход не сделает

Скепсис по делу, чтобы не было завышенных ожиданий.

Гео-списки врут. geoip-ru — это база принадлежности IP, и она отстаёт от реальности. Российский сервис за иностранным CDN в неё не попадёт, а иностранный сервис на российском хостинге попадёт зря. Готовьтесь дополнять список руками через inline rule-set.

CDN общий на всех. Один и тот же адрес Cloudflare или Akamai обслуживает и заблокированный, и разрешённый ресурс. Правило по IP тут бессильно в принципе — спасает только маршрутизация по домену, то есть работающий sniff.

Маршрутизация не лечит блокировку самого протокола. Если DPI душит ваше соединение с сервером, никакие правила на клиенте не помогут — проблема ниже по стеку. Это отдельная тема, разобранная в диагностике блокировок по симптомам.

Реклама режется не вся. reject на уровне соединения не трогает то, что приходит с того же домена, что и контент — YouTube тому примером.


Источники (всё — официальная документация проекта)

Question:

Какой стратегии придерживаетесь вы — белый список, чёрный или «всё через прокси и не думать»? И интересен практический опыт: чем дополняете geosite-category-ru / geoip-ru руками, какие сервисы регулярно вылетают из этих списков и ломаются?