Давайте дружить в Телеграме: рассказываем про новые фичи и общаемся в комментах Подписаться
support@serv.host
Личный кабинет

Xray core на пальцах: xmux, mux, fragment, noise и как это выглядит в трёх боевых конфигах.

Вообще я микроразработчик, со мной можно связаться как угодно, я открыт ко всему

Сторонний мой проект: Zapret2UI.


Если хоть раз тащили себе чужой конфиг для VLESS и Xray - вы точно натыкались на весь этот зоопарк из mux, xmux, fragment, noise, finalmask и ещё десятка похожих на случайный набор букв параметров. Работает - и слава богу, никто разбираться не лезет в них потому что это выглядит сложно. Но рано или поздно то ли ломается на ровном месте, то ли накрывает мысль: сколько ещё быть тем самым чуваком, который копипастит конфиг с форума, зажмуривается и молится, чтобы завелось?

Разберем: что каждая настройка реально делает, откуда вообще взялась - и сразу же, без отрыва от жизни, посмотрим на трёх реальных боевых конфигах, как это выглядит на практике и, самое главное, чем один технически лучше или хуже другого, а не просто «все рабочие, берите любой, вам всё равно норм будет».

Чуть сразу надо сделать важную оговорку. Конфиги действительно рабочие, но я не даю никому никакие гайды, советы как лучше и пр. и не собираюсь, я могу помочь, но всё в рамках разумного.

Сразу думаю надо сказать, что первые два представленных конфига - не мои, с реальным продакшеном (секреты, понятно, подчищены, никто чужим UUID не пользуется), третий - выцепил из публичного обсуждения в самом репозитории Xray-core, потому что он показывает третий, совсем разбойничий подход: не выбирать между первыми двумя, а нагло натянуть оба сразу на один порт.

Вообще самая нормальная тема - делать всё самому и изучать что-то новое, как всё работает и т.д

Кому пригодится: если уже гоняете VLESS/Xray и тащите конфиги с форумов, не до конца понимая половину строчек; если выбираете между Reality и связкой TLS плюс nginx и хотите не на ощупь тыкать, а по фактам; или если просто любите разбирать чужие наработки - тут будет из чего собрать своё решение.

Секреты (UUID, ключи Reality, seed-строки шифрования) везде заменены на плейсхолдеры вида <UUID> - тащить в статью боевые значения смысла нет, а вот структура и логика конфигов сохранена один в один, так что справитесь с составлением своего конфига.


Содержание

  1. Зачем в конфиге вообще эти строчки
  2. Два конфига на столе
  3. Конфиг первый построчно
  4. Конфиг второй построчно: только отличия
  5. Конфиг первый против второго: техническое сравнение
  6. Конфиг третий: а если не выбирать
  7. Конфиг третий построчно
  8. Mux и XMUX: попутка вместо личного такси
  9. Частая ошибка: mux.cool вместе с XHTTP
  10. Fragment: рвём TLS ClientHello на куски
  11. Честно про fragment: не вечная отмычка
  12. Noise и FinalMask: почему их нигде тут нет
  13. Padding и Seed: два разных слоя маскировки размера
  14. Главные параметры XHTTP построчно
  15. Xmux: разные философии тюнинга одного и того же
  16. Что выбрать под свой случай
  17. Частые ошибки новичка
  18. Где попробовать самому
  19. Короткий итог

Источники


Зачем в конфиге вообще эти строчки

У любой связки VPN/прокси есть база - протокол и транспорт, которые тащат ваши данные из точки А в точку Б. А есть надстройки поверх этой базы, и вот они как раз и называются всеми этими сложными словами mux, fragment, noise. Каждая затыкает свою узкую дыру, далее разберу что за что отвечает:

  • Mux/XMUX - про эффективность: не плодить лишние соединения, не сливать время на их установку.
  • Fragment - про форму: не дать распознать характерный силуэт TLS-рукопожатия.
  • Noise/FinalMask - про шум: размыть общий рисунок трафика посторонними пакетами.
  • Padding/Seed - про размер: не дать угадать, что происходит, по толщине пакетов.

Отдельно всё разберем ниже, не хочу много рассказывать сразу, поэтапно пойдем

Так же важно понимать, ни одна из этих настроек не является обязательной, всё лишь на ваш личный выбор. Но каждая закрывает конкретную дыру, и как только понимаете какую именно - пропадает нужда слепо тащить чужой конфиг «на всякий случай», обвешивая себя всем подряд как новогоднюю ёлку.


Два конфига на столе

Конфиг №1 - свой домен, обычный TLS, сервер сидит за nginx:

{
  "outbounds": [{
    "protocol": "vless",
    "tag": "proxy",
    "streamSettings": {
      "network": "xhttp",
      "security": "tls",
      "tlsSettings": {
        "alpn": ["h2"],
        "fingerprint": "random",
        "serverName": "example-domain.com"
      },
      "xhttpSettings": {
        "host": "example-domain.com",
        "path": "/videos/media/",
        "mode": "stream-one",
        "headers": { "X-Request-ID": "<ваш код>" },
        "uplinkHTTPMethod": "POST",
        "seqKey": "X-Sequence-Index",
        "seqPlacement": "header",
        "sessionIDKey": "X-Correlation-ID",
        "sessionIDLength": "16-32",
        "sessionIDPlacement": "header",
        "sessionIDTable": "Base62",
        "xPaddingBytes": "128-1120",
        "xPaddingMethod": "tokenish",
        "xPaddingObfsMode": true,
        "xPaddingPlacement": "cookie",
        "xPaddingHeader": "X-Cache-Status",
        "xmux": {
          "maxConcurrency": "0",
          "maxConnections": "1-3",
          "cMaxReuseTimes": "300-600",
          "hKeepAlivePeriod": 600,
          "hMaxRequestTimes": "1000-2000",
          "hMaxReusableSecs": "1200-2400"
        }
      },
      "finalmask": { "tcp": [{ "type": "fragment", "settings": {
        "packets": "tlshello",
        "lengths": ["2-3","3-6","5-9","7-12","10-16","18-28"],
        "delays": ["0-5","0-8","5-10","8-15","0-12","3-10"],
        "maxSplit": "18-24"
      }}]},
      "sockopt": { "tcpFastOpen": true, "tcpcongestion": "bbr" }
    },
    "settings": {
      "address": "example-domain.com",
      "port": 443,
      "id": "<UUID>",
      "flow": "xtls-rprx-vision",
      "encryption": "<ml-kem-encryption-string>"
    }
  }]
}

Конфиг первый построчно

Чтобы не гадать, что значит каждая строчка - вот расшифровка конкретно этого конфига, поле за полем. Разжёвано, а не «ну вы поняли»:

ПолеЧто делает
security: "tls"Обычный настоящий TLS - сервер сам держит сертификат, Reality тут вообще не при делах
tlsSettings.alpn: ["h2"]Честно сообщаем, что поверх TLS пойдёт HTTP/2 - иначе для XHTTP это будет подозрительно
tlsSettings.fingerprint: "random"При каждом соединении клиент через uTLS подделывает TLS-отпечаток под случайный настоящий браузер(Важная особенность именно этого конфига). Суть такова, что, здесь он прикидывается видеохостингом/стриминг сервисом. Люди не могут заходить на него только под 1 типом браузера. Сам по себе random фингерпринт не так хорош, но это уже отдельная история.
tlsSettings.serverName / xhttpSettings.hostВаш домен - тот самый, на который выписан сертификат
xhttpSettings.path: "/videos/media/"URL-путь эндпоинта, замаскирован под раздачу видео/медиа - легенду надо выдерживать и тут, не абы как
mode: "stream-one"Самый прямой из трёх режимов XHTTP, без разбивки на пронумерованные куски (разбор ниже, в главе про параметры XHTTP)
headers: { "X-Request-ID" }Кастомный заголовок замаскирован под трассировочный ID запроса - такое реально гоняют бэкенды, глаз не цепляется
uplinkHTTPMethod: "POST"Каким методом клиент шлёт данные наверх
seqKey: "X-Sequence-Index"В этом заголовке едет номер очерёдности пакета - нужен для правильной пересборки на сервере
sessionIDKey: "X-Correlation-ID"Идентификатор сессии - тоже под трассировочный заголовок, «Correlation ID» реально расхожий термин в микросервисах
sessionIDLength: "16-32"Длина этого ID - случайно от 16 до 32 символов
sessionIDTable: "Base62"Алфавит генерации ID: буквы плюс цифры, выглядит как обычный session ID любого сайта
xPaddingBytes: "128-1120"Сколько мусорных байт паддинга добавляется к запросам
xPaddingMethod: "tokenish"Мусор генерируется похожим на случайный токен, а не тупым повторяющимся заполнителем
xPaddingPlacement: "cookie"Паддинг прячется в cookie
xPaddingHeader: "X-Cache-Status"Имя, под которым паддинг едет - тут замаскирован под служебный заголовок CDN или кэша, такое реально отдают сайты за Cloudflare или Varnish, не выделяется вообще
xmux { ... }Пул и поведение TCP-соединений под транспортом - цифры разберём отдельно, в главе про xmux, там есть о чём поговорить
finalmask.tcp[0].type: "fragment"Дробим TLS ClientHello на куски - подробно в главе про fragment ниже
sockopt.tcpFastOpen: trueTCP Fast Open - клиент кэширует cookie ещё с первого хендшейка к серверу и в каждом следующем новом соединении шлёт данные сразу в SYN-пакете, не дожидаясь конца рукопожатия. Работает на любое новое соединение к уже знакомому серверу, необязательно на буквальное переподключение - при постоянной пересборке TCP-соединений.
sockopt.tcpcongestion: "bbr"Алгоритм управления перегрузкой TCP, задаётся явно поверх системного - но только на Linux, и смысл в этом есть, только если BBR на хосте вообще доступен. Если он и так стоит по умолчанию в системе, поле можно смело не трогать - Xray и без него унаследует то, что выбрано на уровне ядра (sysctl net.ipv4.tcp_congestion_control покажет, что там сейчас). bbr Сейчас является рекомендуемым по заявлениям автора.
settings.port: 443Стандартный HTTPS-порт, чтобы не отсвечивать
settings.idВаш UUID - идентификатор клиента на сервере
settings.flow: "xtls-rprx-vision"Включает Vision - маскирует TLS-в-TLS отпечаток (разбирали в статье про сравнение протоколов)
settings.encryptionСтрока VLESS Encryption - постквантовый ключевой обмен ML-KEM поверх X25519

Конфиг №2 - Reality напрямую, донор - чужая инфраструктура (видеостриминг Яндекса):

{
  "outbounds": [{
    "protocol": "vless",
    "tag": "proxy",
    "streamSettings": {
      "network": "xhttp",
      "security": "reality",
      "realitySettings": {
        "fingerprint": "firefox",
        "serverName": "strm-rad-23.strm.yandex.net",
        "publicKey": "<REALITY_PUBLIC_KEY>",
        "shortId": "<shortId>",
        "spiderX": "<spiderX-путь>",
        "mldsa65Verify": "<mldsa65-ключ>",
        "show": false
      },
      "xhttpSettings": {
        "host": "runtime.strm.yandex.ru",
        "path": "/player/video/",
        "mode": "stream-one",
        "uplinkHTTPMethod": "POST",
        "seqKey": "X-Connection-ID",
        "seqPlacement": "header",
        "sessionIDKey": "X-Strm-Session",
        "sessionIDLength": "16-32",
        "sessionIDPlacement": "header",
        "sessionIDTable": "Base62",
        "xPaddingBytes": "128-1120",
        "xPaddingMethod": "tokenish",
        "xPaddingObfsMode": true,
        "xPaddingPlacement": "header",
        "xPaddingHeader": "X-Strm-Log-Split",
        "xmux": {
          "maxConcurrency": "0",
          "maxConnections": "1-3",
          "cMaxReuseTimes": "300-600",
          "hKeepAlivePeriod": 600,
          "hMaxRequestTimes": "1000-2000",
          "hMaxReusableSecs": "1200-2400"
        }
      },
      "finalmask": { "tcp": [{ "type": "fragment", "settings": {
        "packets": "tlshello",
        "lengths": ["3-5","4-7","6-10","8-14","12-18","16-24"],
        "delays": ["0-8","2-10","5-12","0-15","8-15","5-10"],
        "maxSplit": "20-26"
      }}]},
      "sockopt": { "tcpFastOpen": true, "tcpcongestion": "bbr" }
    },
    "settings": {
      "address": "example-domain.com",
      "port": 443,
      "id": "<UUID>",
      "flow": "xtls-rprx-vision",
      "encryption": "<ml-kem-encryption-string>"
    }
  }]
}

Конфиг второй построчно: только отличия

Половина полей тут дословно совпадает с конфигом №1 (xmux, sockopt, mode, uplinkHTTPMethod и так далее) - смысл ровно тот же, расшифровку смотрите в таблице выше, дублировать незачем. А вот что реально другое:

ПолеЧто делает
security: "reality" + realitySettingsВместо своего TLS - Reality. Блок tlsSettings пропадает целиком, его место занимает этот
realitySettings.fingerprint: "firefox"В отличие от "random" у первого конфига, тут отпечаток зафиксирован под конкретный браузер - логично, раз косплеим одного конкретного донора, а не мельтешим
realitySettings.serverNameДонор - чью TLS-личность одалживаем на лету (тут - видеостриминг Яндекса)
realitySettings.publicKey / shortIdПубличная половина ключевой пары Reality и короткий ID для различения клиентов на сервере
realitySettings.spiderXПуть, по которому клиент дополнительно «прогуливается» по донору при подключении, имитируя обычное поведение браузера, а не голую тишину
realitySettings.mldsa65VerifyПостквантовая подпись поверх обычного обмена ключами - задел на будущее, разбирали чуть выше
host / path / seqKey / sessionIDKey / xPaddingHeaderВсё названо в стилистике донора (X-Strm-Session, X-Strm-Log-Split и так далее) - раз косплеим видеостриминг, то и внутренняя кухня XHTTP должна звучать соответствующе, а не как рандомный набор букв
xPaddingPlacement: "header"В отличие от "cookie" у первого конфига, тут паддинг едет прямо в заголовке
fragment.lengths / delays / maxSplitТе же по смыслу параметры, что у конфига №1, просто с другими конкретными диапазонами - и так и должно быть, одинаковые цифры на двух разных серверах смотрелись бы подозрительно

Первое, что бросается в глаза, если положить их рядом: транспорт одинаковый - xhttp. Разница не в том, «как передаём», а в одном-единственном поле - security: "tls" или "reality". Из этой одной строчки и вырастают вообще все дальнейшие отличия, и сейчас разберём их поподробнее, с цифрами и прочем.


Конфиг первый против второго: техническое сравнение

Вот тут - именно то сравнение, которого я думаю ждут многие, постораюсь расписать нормально.

  • Домен и сертификат. У конфига №1 - свой домен и TLS-сертификат, продление раз в ~90 дней (Let's Encrypt), постоянная головная боль (хотя на самом деле не такая уж и боль, но всё же думаю упомянуть надо). У конфига №2 не нужны вообще - Reality одалживает чужой сертификат на лету, никакого продления.
  • Совместимость с CDN. Конфиг №1 - да, можно спрятать origin-IP за Cloudflare или аналогом. Конфиг №2 - физически нет: Reality сама реализует TLS-рукопожатие, обрыв TLS прокси перед ней ломает всё в хлам.
  • Реальный IP сервера. У конфига №1 скрыт, если стоит за CDN, иначе виден. У конфига №2 - всегда на виду у того, кто уже знает адрес или его подберёт.
  • Устойчивость к активному пробингу. У конфига №1 это целиком на вашей совести: если по /videos/media/ не лежит ничего похожего на настоящий видеосервис - спалились, думаю тут очевидно, что ВАЖНО это учитывать. У конфига №2 - максимальная из коробки: пробинг утыкается в настоящий Яндекс, вы тут вообще ни при чём.
  • Зависимость от третьей стороны. У конфига №1 - ноль, домен и сертификат целиком ваши. У конфига №2 - есть: если донор поменяет TLS-поведение, ALPN или сертификат, маскировка может треснуть без вашего ведома, и вы узнаете об этом постфактум.
  • Число подвижных частей. У конфига №1 больше: DNS, сертификат, nginx, по-хорошему ещё CDN. У конфига №2 меньше: сам Xray плюс пара Reality-ключей, и всё.
  • Лишний хоп в цепочке. У конфига №1 - да, через nginx (терминирует TLS сам или гонит поток дальше). У конфига №2 - нет, Xray сам держит TLS-сокет напрямую.
  • Простота развёртывания. Конфиг №1 муторнее: домен, DNS, выпуск сертификата, конфиг nginx (основная, думаю, проблема), ну и так же без CDN не много смысла имеет, важно учитывать это. Конфиг №2 в разы проще: сгенерировал ключи, вписал донора - готово.

Если сжать до одной мысли: конфиг №1 прячет сам факт, что сервер вообще существует по этому адресу, конфиг №2 прячет, что происходит на уже известном адресе. Это разные задачи, и путать их не стоит - взяли не тот конфиг под свою угрозу, получите красивую, но абсолютно бесполезную маскировку. Как надеть камуфляж и сесть с ним посреди чистого поля.

Про второе как известная гифка с ослом который раскурил на max.ru:443


Конфиг третий: а если не выбирать

А теперь то, ради чего вообще стоило рыть глубже в поисках третьего примера: в комьюнити Xray-core давно гоняют конфиги, которые вообще не выбирают между «свой домен» и «чужая личность», а нагло натягивают оба варианта на один и тот же порт 443 одновременно. Вот упрощённая суть (плейсхолдеры - как в оригинале, полный конфиг - в источниках):

{
  "inbounds": [
    {
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [{ "id": "<UUID-прямых>", "flow": "xtls-rprx-vision" }],
        "fallbacks": [{ "dest": "/dev/shm/xhttp.sock", "xver": 0 }]
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality",
        "realitySettings": {
          "target": "/dev/shm/nginx.sock",
          "serverNames": ["a.yourdomain.com"],
          "privateKey": "<приватный ключ>",
          "shortIds": ["<shortId>"]
        }
      }
    },
    {
      "listen": "/dev/shm/xhttp.sock,0666",
      "protocol": "vless",
      "settings": { "clients": [{ "id": "<UUID-xhttp>" }], "decryption": "none" },
      "streamSettings": {
        "network": "xhttp",
        "xhttpSettings": { "path": "/xhttp_upload", "mode": "auto" }
      }
    }
  ]
}

Конфиг третий построчно

Тут структура другая - не один outbound, а два inbound'а, поэтому и таблица короче, но логика та же:

ПолеЧто делает
port: 443 (первый inbound)Стандартный HTTPS-порт - та самая единая дверь, куда стучатся вообще все, и прямые клиенты, и гости через CDN
clients[].flow: "xtls-rprx-vision"UUID и Vision - для тех, кто идёт напрямую, без всякого CDN на пути
fallbacks.dest: "/dev/shm/xhttp.sock"Куда падает всё, что не прошло как валидный Vision-хендшейк - на unix-сокет второго inbound'а
network: "raw"Голый TCP без обёрток - именно это и нужно Reality, чтобы держать TLS-хендшейк напрямую, руками
security: "reality"Тот же Reality, что и в конфиге №2, только смотрит он не на чужого донора, а...
realitySettings.target: "/dev/shm/nginx.sock"...на собственный локальный nginx на unix-сокете. Reality тут не одалживает чужую личность, а честно показывает то, что реально стоит на сервере
realitySettings.serverNamesДомен, под который замаскирован сервер - тот же самый, что видит и nginx, и клиенты, пришедшие через CDN
realitySettings.privateKey / shortIdsТо же самое, что publicKey/shortId в конфиге №2, только тут приватная половина ключа - она живёт только на сервере и наружу не торчит
listen: "/dev/shm/xhttp.sock,0666" (второй inbound)Второй вход - тот самый unix-сокет, куда падают фолбэки. 0666 - права доступа на файл сокета, чтобы nginx (обычно работающий от другого пользователя) вообще мог в него писать
decryption: "none"На этом внутреннем инбаунде VLESS Encryption не используется - сюда трафик приходит уже прикрытый внешним TLS через nginx, свой слой шифрования поверх ни к чему
xhttpSettings.mode: "auto"Режим выбирается автоматически, не зафиксирован жёстко, как stream-one в конфигах №1 и №2

Как бы вернее описать что выше. На порту 443 висит Reality, но её target смотрит не наружу, а на локальный unix-сокет с nginx. Дальше сервер сам, без вашего участия, разруливает, кто к нему пришёл:

  • Клиент с правильным Vision-хендшейком и своим UUID - обслуживается напрямую, минимальная задержка, максимум скорости, никаких посредников. Это, по сути, конфиг №2 из этой статьи, только вместо чужого донора target смотрит на свой же nginx.
  • Все остальные (в первую очередь - трафик, который прошёл через CDN и там уже был переупакован, живой Vision-хендшейк такое просто не переживёт) падают по fallbacks во второй inbound - обычный XHTTP на том же unix-сокете. Это, по сути, конфиг №1: тот же сервер, тот же домен, только для гостей через парадный вход с общим CDN-подъездом.

Красиво звучит, но будем честны, без прикрас и муторнее в развёртывании: два inbound'а, unix-сокеты, нужно аккуратно настроить nginx так, чтобы он и внешний CDN-трафик принимал, и Reality target корректно отдавал. Плата за то, чтобы не разрываться между «быстро и напрямую» и «спрятано за CDN» - лишний слой конфигурации, за которым потом придётся следить и который первым же и сломается, если что-то пойдёт не так.

Вообще такой вам не нужен, я как пример жесткого конфига просто добавил его))


Mux и XMUX: попутка вместо личного такси

Каждое новое TCP-соединение - это своё рукопожатие, свой TLS-хендшейк, свои накладные расходы по времени. Приложение открывает пачку мелких запросов подряд (браузер так и делает - страница плюс двадцать картинок и скриптов) - плодить под каждый своё отдельное соединение дорого по времени, муторно и попросту глупо, именно по этому будем использовать mux/xmux.

Mux (mux.cool) пакует несколько логических соединений в одно физическое. Как маршрутка вместо личного такси на каждого пассажира:

"mux": { "enabled": true, "concurrency": 8 }

У попутки есть обратная сторона: если одна из «поездок» внутри подвисла, это аукается и остальным пассажирам той же машины (тот же эффект пробки из-за одной аварии, что и у TCP-over-TCP - разбирали в статье про сравнение протоколов). На толстом стабильном канале mux иногда мешает больше, чем помогает - тащите балласт, а профита ноль.

XMUX - та же идея, но заточенная конкретно под XHTTP, и в обоих наших конфигах он именно там и живёт. Вместо одной старой раздолбанной маршрутки, которая едет пока не развалится будем использовать нормальное приложение с целым пулом машин: сколько держать в резерве, когда вызывать новую, когда старую списывать на металлолом. Про конкретные цифры и разные подходы к тюнингу - отдельным разделом ниже, там будет с чем сравнить и порассуждать.


Частая ошибка: mux.cool вместе с XHTTP

Чуть разбор популярных ошибок. Если транспорт - XHTTP, не вешайте сверху ещё и классический mux.cool. XHTTP уже мультиплексирует через xmux сам, это часть транспорта. Добавить mux.cool поверх - это посадить пассажиров в маршрутку, а потом попытаться рассадить их ещё и по попуткам сверху: как минимум бессмысленно, как максимум - конфликт и лишние баги на абсолютно ровном месте, которые потом будете полдня отлавливать.

Правило простое: mux.cool - для транспортов, которые сами не умеют мультиплексировать(классический WebSocket, например). Для XHTTP - не трогайте, там уже всё есть.


Fragment: рвём TLS ClientHello на куски

При установке TLS-соединения первым делом улетает пакет ClientHello - в открытую говорит, какой сайт вы хотите открыть (через SNI), какие шифры поддерживаете. Наблюдателю даже не нужно ничего расшифровывать - хватает одного этого пакета, чтобы всё понять и вынести вердикт, по вашей грешной душе.

Fragment дробит его на куски поменьше, разбрасывая их отдельными TCP-сегментами с паузами между ними. В теории - одна пара чисел на размер и задержку. На практике, в наших конфигах, схема куда серьёзнее - вот кусок из конфига №2:

"fragment": {
  "packets": "tlshello",
  "lengths": ["3-5","4-7","6-10","8-14","12-18","16-24"],
  "delays": ["0-8","2-10","5-12","0-15","8-15","5-10"],
  "maxSplit": "20-26"
}

Вместо одного фиксированного диапазона - целая палитра вариантов, из которой каждый раз выбирается что-то новое, случайным образом:

  • lengths - набор диапазонов размера кусков, от совсем мелких до покрупнее.
  • delays - набор диапазонов пауз между кусками в миллисекундах.
  • maxSplit - потолок на то, сколько всего кусков максимум получится из одного пакета.

Смысл в том же, что и у базовой версии - наблюдатель, который разбирает каждый TCP-сегмент по отдельности «на лету», видит только жалкий обрывок и не может опознать характерную структуру целиком. Но с палитрой вариантов ваше поведение ещё и непредсказуемо от соединения к соединению - насмотревшись на пару ваших коннектов, не получится вычислить один и тот же повторяющийся паттерн и заскриптовать под него детект.

Я примеры кроме разбора конфига давать не буду, думаю помните. Вроде аккуратно выразился и расписал всё без потери смысла.


Честно про fragment: не вечная отмычка

Чуть разберем в принципе важную часть данного параметра. Фрагментация - это гонка вооружений, а не разовое решение задачи раз и навсегда. По полевым отчётам за 2026 год часть систем анализа трафика уже научилась пересобирать фрагментированные TCP-сегменты обратно в один поток, прежде чем анализировать содержимое, то есть именно та защита, которую даёт fragment, для них уже не работает так, как раньше.

Не относитесь к нему как к настройке «включил и забыл навсегда, теперь я неуязвим». Работает сегодня - не гарантия, что будет работать завтра в том же виде: с обеих сторон идёт постоянная гонка, и никто вам заранее не напишет в личку, брат, там сейчас упадет же у тебя сервер, знал?). Касается вообще любого приёма из этой статьи, но про fragment сказать отдельно особенно важно - слишком часто его подают как незыблемую вещь, а это не так и никогда так не было. Смена одного значения дает вам доступ к тому, что казало не работает.


Noise и FinalMask: почему их нигде тут нет

Если читали статью про сравнение протоколов - там был приём у AmneziaWG: перед реальным рукопожатием кинуть пачку мусорных пакетов случайного размера, чтобы не было видно, где именно начался настоящий разговор. Noise в Xray-core - концептуально та же идея, но доведённая до отдельной подключаемой системы под названием FinalMask: набор пригодных блоков (noise, salamander, sudoku, xdns, xicmp и другие), которые комбинируются под сценарий, как конструктор.

Обратите внимание: ни в одном из конфигов этой статьи noise не используется - везде вместо него именно fragment. И это не случайность, а логика: noise и соседние блоки FinalMask заточены в первую очередь под сырой UDP-трафик (mKCP и похожие транспорты), где нет прикрытия в виде TLS/HTTP - там действительно нужно шумом размывать сам факт передачи. А у нас везде транспорт TCP-based (XHTTP либо TCP-Reality) - трафик и так одет в TLS (настоящий, чужой или свой), и главная проблема тут не «спрятать сам факт передачи», а «не спалиться на форме одного конкретного пакета» - вот с этим справляется именно fragment. Noise тут был бы лишним оверхедом без реальной пользы.

Правило простое: каждая настройка - ответ на конкретную угрозу под конкретный транспорт. Нет угрозы - нет смысла в настройке.


Padding и Seed: два разных слоя маскировки размера

Отдельная история от noise, хоть и близкая по назначению. В конфигах два разных паддинга, и их легко перепутать, если не разбираться:

  • Seed у Vision (через encryption в связке VLESS Encryption) - маскирует внутренний ритм уже установленного TLS-соединения, ту самую «толщину» пакетов, которая выдаёт TLS-в-TLS, если внутри вашего туннеля едет ещё один слой шифрованного трафика.
  • xPadding* у XHTTP - паддинг конкретно транспортного уровня, отдельный механизм. В обоих конфигах диапазон 128-1120 байт мусора добавляется к запросам, xPaddingMethod: "tokenish" генерирует его похожим на случайный токен, а не тупо повторяющимся мусором (это и есть альтернатива дефолтному repeat-x).

В комьюнити встречается и совсем наглый вариант: класть паддинг не в отдельный служебный заголовок, а прямо в заголовок Referer (xPaddingPlacement: "queryInHeader", xPaddingHeader: "Referer") - мусорные байты выглядят как обычная ссылка перехода, а не как что-то специально прикрученное для маскировки, вот это реально красивый ход. У наших конфигов паддинг честно едет в кастомном заголовке или cookie - тоже рабочий вариант, просто чуть более узнаваемый на фоне трафика к обычному сайту.

Разница между двумя слоями простая: Seed размывает рисунок уже внутри одного соединения, а xPadding - размывает размер самих HTTP-запросов транспорта. Работают на разных уровнях, друг другу не мешают и в наших конфигах используются вместе - и правильно делают.


Главные параметры XHTTP построчно

Раз конфиги №1 и №2 гоняют один и тот же транспорт, разберём общие для XHTTP параметры разом - устроены одинаково в обоих, чего добру пропадать.

  • mode: "stream-one" - один из трёх режимов XHTTP (packet-up, stream-up, stream-one). stream-one - самый прямой, без разбиения на пронумерованные куски; логично, когда путь и так напрямую до сервера (через nginx) или до донора (Reality), без непредсказуемого CDN на пути, которому нужна была бы устойчивость packet-up.
  • uplinkHTTPMethod: "POST" - каким методом клиент шлёт данные наверх. POST - естественный выбор, GET для тела данных вообще не подходит по смыслу самого HTTP, думаю тут даже пояснять не надо.
  • seqKey / seqPlacement - имя заголовка и место, куда XHTTP кладёт номер последовательности запроса (нужен для правильной пересборки потока на сервере, если запросы вдруг придут не по порядку).
  • sessionIDKey / sessionIDPlacement / sessionIDLength / sessionIDTable - похожая история, но для идентификатора сессии: в каком заголовке едет, какой длины (в обоих - случайно от 16 до 32 символов) и из какого алфавита генерируется (Base62 - буквы плюс цифры, выглядит как обычный session ID любого веб-сервиса, а не как что-то специфичное для прокси, торчащее наружу флагом).

    Вообще base62 выглядит лично для меня сттранным, т.к слишком уж большой, но заметным его не делает, на данный момент никто такие анализы не делает

  • xPaddingBytes / xPaddingMethod / xPaddingObfsMode / xPaddingPlacement / xPaddingHeader - см. предыдущий раздел, тут просто где физически лежит паддинг: где-то в заголовке, у первого конфига ещё доступен вариант класть в cookie.

Общая логика у всей этой группы одна: количество, размер и последовательность запросов не должны выдавать проксирование характерным ритмом, а сама структура (имена заголовков, алфавит ID) не должна торчать инородно на фоне остального трафика к серверу.

У конфига №2 это доведено до максимума: даже заголовки (X-Strm-Session, X-Strm-Log-Split) названы в стилистике донора - раз уж косплеим видеостриминг Яндекса, то и внутренняя кухня XHTTP звучит соответствующе, а не как случайный набор букв, который любой внимательный глаз тут же выцепит.

Отдельно стоит знать про режим stream-up - его в наших конфигах нет, но в боевых конфигах он встречается часто, когда аплинк и даунлинк разносят по разным настройкам (downloadSettings) или даже разным путям/адресам. В связке с включённым MPTCP (Multipath TCP, когда одно соединение фактически едет сразу по нескольким сетевым путям) это отдельная, более гибкая философия - за счёт куда более сложной настройки, разумеется, бесплатных плюшек тут не бывает.

Я надеюсь, что смог понятно разъяснить эти вещи


Xmux: разные философии тюнинга одного и того же

Оба наших конфига используют одинаковый набор значений:

"xmux": {
  "maxConcurrency": "0",
  "maxConnections": "1-3",
  "cMaxReuseTimes": "300-600",
  "hKeepAlivePeriod": 600,
  "hMaxRequestTimes": "1000-2000",
  "hMaxReusableSecs": "1200-2400"
}
  • maxConcurrency: "0" - на деле это не лимит, а переключатель философии: maxConcurrency и maxConnections по документации XHTTP - взаимоисключающая пара, используется либо одно, либо другое. Обнулили maxConcurrency - осознанно отказались от схемы «много параллельных потоков в одном соединении» в пользу схемы «управляемый пул отдельных соединений», которым дальше и рулит maxConnections.
  • maxConnections: "1-3" - пул из одного-трёх TCP-соединений одновременно. Небольшой пул - осознанный выбор: меньше отдельных TCP-хендшейков на виду, трафик собирается в меньшее число.
  • cMaxReuseTimes: "300-600" - сколько раз соединение переиспользуется, прежде чем его закроют и откроют новое. Случайный разброс - чтобы момент тоже не был предсказуемым по чёткому счётчику, который легко засечь.
  • hKeepAlivePeriod: 600 - интервал keep-alive пинга в секундах, чтобы HTTP/2-соединение не отваливалось от простоя. По умолчанию тут 0 - и это не «пинги выключены», а «отдаём keepalive на откуп самому HTTP/2 или HTTP/3 клиенту» (у Chrome на H2 это около 45 секунд, у quic-go на H3 - около 10). Кстати, это единственное поле xmux без поддержки диапазона - рандомизировать таймер keepalive смысла нет, а вот отрицательное значение (например, -1) как раз и выключает пинги по-настоящему.
  • hMaxRequestTimes / hMaxReusableSecs - ещё два потолка для того же соединения: по числу запросов и по времени жизни. Тройная подстраховка - какой из трёх лимитов сработает первым, тот и пересоздаёт соединение, остальным можно спокойно отдыхать.

А вот другая связка, которая гуляет по обсуждениям Xray-core для похожего сценария (XHTTP плюс Reality, но с stream-up и раздельными настройками аплинка и даунлинка):

"xmux": {
  "maxConcurrency": "16-32",
  "cMaxReuseTimes": 0,
  "hMaxRequestTimes": "600-900",
  "hMaxReusableSecs": "1800-3000",
  "hKeepAlivePeriod": 0
}

И вот тут реально интересно - это вообще другая философия, а не просто другие числа для понтов. Наш конфиг держит мало соединений (1-3), но регулярно их пересоздаёт (300-600 переиспользований - и всё, труба меняется) - это ставка на то, что периодическая смена соединения выглядит органичнее, чем один и тот же TCP-сокет, живущий часами напролёт. А эта альтернативная связка, наоборот, гоняет много параллельных потоков в одном соединении (16-32) и почти никогда не пересоздаёт его (cMaxReuseTimes: 0 и hKeepAlivePeriod: 0 - оба лимита фактически отключены намеренно) - ставка на то, что редкие новые TCP-хендшейки на виду безопаснее, чем частая смена соединений, а вот один долгоживущий сокет с кучей параллельных потоков внутри как раз похож на то, как ведёт себя настоящий браузер с открытой вкладкой у обычного человека.

Важно запомнить никогда не будет исконно рабочего конфига, оба закрывают одну и ту же цель (не палиться характерным поведением соединения) совершенно разными средствами. Что выбрать - зависит от вашего канала и от того, какой профиль поведения на нём будет выглядеть органичнее, а не от того, чьи цифры звучат солиднее в чужом гайде.

Пример: Вот вы маскируетесь под видеохостинг, которому нужно постоянно подгружать данные и плодить соединения? Тогла лучше первый. Вы маскируетесь за условной документацией, тогда вам ко второму, не зачем плодить соединения.


Что выбрать под свой случай

  • Есть свой домен, хочется полного контроля и по-хорошему нужен CDN перед сервером - конфиг №1. Дороже по вложениям (домен, сертификат, желательно ещё CDN), зато прячет сам факт существования вашего origin-сервера, а не только его истинный смысл.
  • Возиться с доменом неохота, а важнее не отличаться от трафика к конкретному крупному сайту - конфиг №2. Проще в развёртывании, устойчивее к активной проверке - но настоящий IP сервера остаётся на виду у всех, кто уже знает или подберёт адрес.
  • Хочется и то, и другое, и не готовы мириться с компромиссом - конфиг №3: один порт, Reality для прямых клиентов, XHTTP-фолбэк для тех, кто идёт через CDN. Плата - заметно больше возни с настройкой и поддержкой, бесплатный сыр только в мышеловке, не будьте глупцами.

Ничто не мешает и просто держать первые два варианта параллельно на разных серверах - собственно, это уже разбиралось в статье про сравнение протоколов как рабочая практика: один профиль тихий и «под прикрытием», другой - запасной на всякий случай, чтобы не остаться совсем без связи.


Частые ошибки новичка

  • Тащат в конфиг всё скопом «для надёжности». Mux, xmux, fragment и noise одновременно, хотя транспорту половина даже не нужна - это лишняя сложность и просадка скорости без реальной пользы, а не защита в квадрате, как многим кажется.
  • mux.cool вместе с XHTTP - см. отдельный раздел выше, самая частая ошибка из всех, наступают на неё стабильно.
  • Копируют чужие числовые параметры не глядя. lengths, delays, maxSplit, значения xmux из чужого гайда подбирались под чей-то конкретный канал - у вас маршрут другой, слепо скопированные цифры не гарантируют такой же эффект, а иногда и вовсе всё портят.
  • Прописывают tcpcongestion вручную не глядя, что на хосте вообще есть. Если BBR и так стоит по умолчанию в системе - поле лишнее, а если его на хосте вообще нет - толку от строчки ровно ноль. Сначала sysctl net.ipv4.tcp_congestion_control, потом уже решение, а не наоборот.
  • Ставят fragment и считают вопрос закрытым навсегда. Уже разобрали - это не разовая настройка, а то, за чем нужно периодически следить, а не поставить и забыть на годы.
  • Не проверяют, что параметры совпадают на клиенте и сервере. Как и с обфускацией у AmneziaWG - если конфиги разошлись, соединение просто не встанет, и разбираться придётся с нуля, проклиная всё на свете.

    Кстати я считаю, что каждый через это должен пройти :)

  • Пытаются повесить Reality за CDN. Не заведётся в принципе, хоть тресни - см. таблицу сравнения выше, это ограничение на уровне самого протокола.

Где попробовать самому

Все эти настройки имеет смысл трогать только на своём сервере, где контролируете обе стороны - клиент и сервер. Тестировать чужой продакшен - вообще не вариант, тыкать fragment и xmux вслепую с чужого маршрута никому не понравится, да и толку с этого ноль.

Удобно поднять тестовый стенд на своём VPS, например на serv.host - завели сервер, накатили Xray, погоняли разные комбинации mux/xmux/fragment и посмотрели по факту, что реально меняет картину на вашем канале, а что просто красиво звучит в чужом гайде.


Короткий итог

  • Mux/XMUX - про эффективность: пакуют несколько логических соединений в одно физическое. XMUX - версия специально под XHTTP, mux.cool туда вешать не надо.
  • Конфиг №1 (свой домен плюс TLS) против конфига №2 (Reality) - разные задачи, не разные уровни «крутости»: первый прячет сам факт существования сервера (особенно за CDN), второй прячет, что происходит на уже известном адресе, и не требует своего домена вообще.
  • Reality никогда не встанет за терминирующим CDN - ограничение на уровне протокола, не обходится никакими настройками, сколько ни колдуй.
  • Можно не выбирать - конфиг №3 показывает, как посадить оба варианта на один порт через Reality с target на локальный nginx и XHTTP-фолбэк, ценой более сложной настройки и постоянного присмотра.
  • Fragment дробит TLS ClientHello на куски по размеру и задержкам - но это стареющий приём, а не вечная гарантия неуязвимости.
  • Noise/FinalMask в этих конфигах не участвуют вообще - они больше про сырой UDP, а не про TCP/XHTTP-сценарии, где и так есть TLS-прикрытие.
  • Xmux можно тюнить по-разному - редкий пул с частой пересборкой соединений или, наоборот, немного долгоживущих соединений с кучей параллельных потоков внутри. Обе стратегии рабочие, спор о правильности - пустая трата времени.

Дальше по любой из тем можно копать глубже - но этого уже достаточно, чтобы открыть чужой конфиг и понимать, что там вообще происходит, а не просто копировать не глядя и молиться, чтобы завелось.


Источники