Актуальные proxy-клиенты на старых Mac часто ведут себя одинаково: если устанавливаются, импортируют профиль, и даже показывают список серверов, не доводят дело до рабочего подключения. На практике это особенно заметно на macOS Catalina 10.15.8. Текущий Happ для Mac официально требует macOS 12.0 или новее, а значит для Catalina уже не подходит как штатный путь. IRBox в релизах заявляет поддержку macOS 10.15+, но в моём тесте на Catalina встроенный core упёрся в несовместимость с системой. Рабочий результат в итоге дал V2RayXS 1.5.9 для x86_64: не через обычный импорт подписки, а через ручную сборку VLESS + Reality outbound.
Дисклеймер
Я не сетевой инженер и не специалист по обходу блокировок. Я написал эту инструкцию как пользователь для таких же пользователей, которые столкнулись с задачей, и не смогли найти в одном месте понятную пошаговую инструкцию. Для тех, кто просто хочет последовательно добиться рабочего результата на старом Mac.
Эта статья рассчитана на ситуацию, когда у вас старый Intel Mac, macOS Catalina 10.15.x, подписка с VLESS-узлами и обычный импорт подписки в клиенте либо не работает, либо создаёт пустой конфиг.

Как определить, какая версия V2RayXS подходит для Catalina
Это важный шаг, который легко пропустить. На старом macOS нельзя просто скачать «самую новую» версию клиента и ожидать, что она будет работать.
Логика выбора для Intel Mac с Catalina 10.15.x была такой:
сначала нужно смотреть не только на название приложения, но и на архитектуру сборки — в нашем случае нужна именно x86_64, а не arm64;
затем нужно проверить release notes старых версий на странице релизов V2RayXS;
если в релизе разработчик сам предупреждает о проблеме именно в x86_64-сборке, такую версию для Catalina лучше не брать;
если в более раннем релизе есть правка, связанная с
realitySettingsили импортом VLESS, такая версия становится приоритетной.
В моём случае это привело к такому выводу:
V2RayXS 1.5.9 x86_64 — лучший кандидат для Catalina;
V2RayXS 1.5.10 x86_64 — нежелателен, потому что сам разработчик предупреждает, что в этой сборке встроен неправильный bundled xray core package;
более новые версии на старом Intel Mac стоит проверять только после явного изучения release notes.
Практическое правило простое: для Catalina сначала искать последнюю стабильную x86_64-сборку до проблемного релиза, а уже потом экспериментировать с более новыми.
Где скачивать старые версии V2RayXS
Искать старые версии лучше в двух местах:
1. Официальные GitHub Releases
Основной источник — страница релизов проекта V2RayXS. Там можно проверить историю версий, changelog и названия файлов.

2. Exact mirror на SourceForge
Для старых сборок V2RayXS есть удобное зеркало на SourceForge. Это полезно, когда GitHub-asset неудобно скачивать напрямую или нужна конкретная старая версия.
Важный нюанс: старые версии V2RayXS обычно распространяются не как DMG, а как ZIP-архив с .app внутри. То есть для Catalina не стоит искать именно DMG любой ценой: для V2RayXS рабочий формат старых релизов — это чаще V2RayXS_x86_64.app.zip.
Схема загрузки такая:
открыть релизы проекта;
найти нужную версию;
выбрать x86_64-сборку;
скачать ZIP-архив;
распаковать его и перенести
.appвApplications.

Что не сработало
Happ
Как основной клиент для Catalina его лучше не брать: текущая версия в Mac App Store требует macOS 12.0 or later. То есть для Catalina этот путь закрыт официально.
IRBox
В релизах IRBox прямо указаны сборки для macOS Intel и Apple Silicon, а в описании релиза сказано, что приложение включает sing-box и xray-core. Формально это выглядит перспективно для Catalina. Но в реальном тесте на 10.15.8 bundled core может оказаться несовместимым с системой.

Что в итоге сработало
Рабочая схема выглядела так:
отсеять клиенты, которые не подходят по macOS или реально не запускаются;
установить V2RayXS 1.5.9 x86_64;
не полагаться на auto-import подписки, если он создаёт пустой
config.json;локально декодировать подписку на самом Mac;
собрать отдельный JSON-outbound для каждого VLESS/Reality-узла;
добавить нужные узлы через
Advanced -> Outbounds;загрузить core и включить
Global Mode;отдельно настроить Telegram Desktop на локальный SOCKS5.
V2RayXS — это GUI для xray-core на macOS. Приложение поднимает локальный SOCKS5 на 127.0.0.1:1081 и HTTP proxy на 127.0.0.1:8001, а также работает в режимах Global Mode, PAC Mode и Manual Mode.

Почему обычный импорт подписки может не сработать
У V2RayXS на Catalina может возникнуть типичная проблема: подписка подтягивается, пункты в меню появляются, но итоговый config.json содержит не реальный узел, а пустую заготовку, например:
address: ""port: 0нулевой UUID
security: "none"пустой
realitySettings
Если вы видите такую картину, значит клиент не собрал полноценный outbound из подписки, и дальше нужно идти через ручной JSON.
"outbounds": [ { "protocol": "vless", "protocol": "vless", "settings": { "vnext": [ { "address": "", "users": [ { "flow": "", "id": "00000000-0000-0000-0000-000000000000", "encryption": "none" } ], "port": 0 } ] }, "tag": "VLESS", "streamSettings": { "security": "none", "realitySettings": {}, "network": "tcp" } } ]
Признаки сломанного импорта: пустой address, port=0, нулевой UUID, security=none и пустой realitySettings.
Как декодировать подписку локально, не публикуя её
Если у вас есть только subscription URL, не публикуйте его и не отправляйте в онлайн-конвертеры. Всё можно сделать локально на самом Mac.
Откройте Terminal и выполните:
curl -L '<SUBSCRIPTION_URL>' | tr -d '\r\n' | base64 -D | nl -ba > ~/Desktop/vless-list.txt open ~/Desktop/vless-list.txt
Что делает эта команда:
скачивает подписку;
декодирует base64;
нумерует все строки;
сохраняет их в
~/Desktop/vless-list.txt.
В итоге вы получаете локальный список всех vless://...-ссылок без публикации URL и без передачи содержимого на сторонние сервисы.

vless-list.txt без использования сторонних сервисовКак выглядит рабочий VLESS + Reality узел
Если взять один такой vless://...-линк и разобрать его на части, в нём обычно есть:
адрес сервера и порт;
UUID;
security=reality;flow=xtls-rprx-vision;sni,pbk,sid,fp, иногдаspx.
Это нужно превратить в JSON-outbound для Xray. Базовый шаблон выглядит так:
{ "protocol": "vless", "tag": "node-001", "settings": { "vnext": [ { "address": "example.com", "port": 443, "users": [ { "id": "UUID-HERE", "encryption": "none", "flow": "xtls-rprx-vision" } ] } ] }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "serverName": "SNI-HERE", "fingerprint": "firefox", "publicKey": "PUBLIC-KEY-HERE", "shortId": "SHORTID-HERE", "spiderX": "/PATH-HERE" } } }
Важно: tag должен быть уникальным. Не используйте main: в V2RayXS это служебный тег.
Как добавить узел вручную в V2RayXS
Откройте Configure... -> Advanced... -> Outbounds.
Дальше:
нажмите
+;вставьте JSON одного узла;
нажмите
Finish;вернитесь в основное окно
Configure...;обязательно нажмите
OK.

Advanced -> OutboundsЕсли после Finish вы не нажмёте OK в основном окне Configure, правка может не сохраниться.

Configure... должны появиться реальные параметры узла, а не пустая заглушкаКак проверить, что конфиг действительно рабочий
Нажмите Load core, затем откройте View current config.json....
После этого в итоговом config.json должно быть видно:
routing.rules[].outboundTagуказывает на ваш тег;в
outboundsуказан реальныйaddress;указан реальный
port;streamSettings.securityравно"reality";realitySettingsзаполнен.
Если всё это есть, значит вы получили не пустой шаблон, а настоящий рабочий Xray-конфиг.
"outbounds": [ { "protocol": "vless", "settings": { "vnext": [ { "address": "example-server.com", "users": [ { "flow": "xtls-rprx-vision", "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "encryption": "none" } ], "port": 443 } ] }, "tag": "reality 1", "streamSettings": { "network": "tcp", "realitySettings": { "publicKey": "PUBLIC_KEY_HERE", "serverName": "example-sni.com", "shortId": "SHORT_ID_HERE", "spiderX": "/example-path", "fingerprint": "firefox" }, "security": "reality" } } ]
Признак правильного импорта: заполнены address, port, UUID, flow, security=reality и блок realitySettings
Как включить подключение
Когда ручной узел уже сохранён и виден в config.json:
выберите нужный сервер в меню V2RayXS;
нажмите
Load core;затем включите
Global Mode.
Если интернет заработал — схема рабочая.
Если интернет пропал:
вернитесь в
Manual Mode;проверьте
config.json;убедитесь, что выбран именно ваш ручной узел, а не пустой шаблон.

Global ModeПочему браузер работает, а Telegram Desktop — нет
Даже если в браузере уже открываются и обычные, и заблокированные сайты, Telegram Desktop может не заработать автоматически. В таком случае проблема обычно не в VLESS и не в сервере, а в том, что Telegram не использует системный proxy V2RayXS так же, как браузер.
Решение:
тип proxy: SOCKS5;
host:
127.0.0.1;port:
1081;логин и пароль: пусто.
После этого Telegram начинает работать через локальный SOCKS5 V2RayXS.

Почему скорость может быть ниже, чем на другом Mac
Если подключение уже работает, но скорость заметно ниже, это не обязательно ошибка настройки. На старом iMac типичные причины такие:
выбран перегруженный узел;
более слабый Intel CPU;
iMac сидит в диапазоне 2.4 GHz, а не 5 GHz;
на другом компьютере используется другой клиент или другой сетевой маршрут.
На практике переход на 5 GHz может дать очень заметный прирост скорости.
Как подготовить JSON для всех VLESS-узлов, не публикуя подписку
Если узлов много, вручную собирать JSON для каждого неудобно. Ниже — локальный Python-скрипт, который:
скачивает подписку по URL;
декодирует её;
находит все
vless://...-ссылки;превращает каждый узел в отдельный JSON-файл для V2RayXS;
ничего не отправляет на сторонние сервисы.
Сохраните файл как ~/Desktop/convert_subscription.py:
#!/usr/bin/env python3 import base64 import json import os import re import sys import urllib.request from urllib.parse import urlparse, parse_qs, unquote def safe_name(s: str) -> str: s = unquote(s).strip() s = re.sub(r'[\\/:*?"<>|]+', "_", s) s = re.sub(r"\s+", " ", s) return s[:80] if s else "node" def first(params, key, default=""): values = params.get(key, []) return values[0] if values else default def vless_to_outbound(link: str, index: int) -> dict: p = urlparse(link.strip()) if p.scheme.lower() != "vless": raise ValueError("Not a vless link") params = parse_qs(p.query) tag = f"node-{index:03d} {safe_name(p.fragment)}".strip() user = { "id": p.username or "", "encryption": first(params, "encryption", "none"), } flow = first(params, "flow", "") if flow: user["flow"] = flow outbound = { "protocol": "vless", "tag": tag, "settings": { "vnext": [ { "address": p.hostname or "", "port": p.port or 443, "users": [user] } ] }, "streamSettings": { "network": first(params, "type", "tcp"), "security": first(params, "security", "none") } } if outbound["streamSettings"]["security"] == "reality": reality = { "serverName": first(params, "sni", ""), "fingerprint": first(params, "fp", ""), "publicKey": first(params, "pbk", ""), "shortId": first(params, "sid", ""), "spiderX": unquote(first(params, "spx", "")), } reality = {k: v for k, v in reality.items() if v} outbound["streamSettings"]["realitySettings"] = reality return outbound def main(): if len(sys.argv) != 2: print("Usage: python3 convert_subscription.py '<SUBSCRIPTION_URL>'") sys.exit(1) sub_url = sys.argv[1] with urllib.request.urlopen(sub_url) as r: raw = r.read().decode("utf-8", errors="ignore").strip() decoded = base64.b64decode(raw).decode("utf-8", errors="ignore") links = [line.strip() for line in decoded.splitlines() if line.strip().lower().startswith("vless://")] if not links: print("No VLESS links found.") sys.exit(2) out_dir = os.path.expanduser("~/Desktop/v2rayxs-outbounds") os.makedirs(out_dir, exist_ok=True) index_lines = [] for i, link in enumerate(links, start=1): outbound = vless_to_outbound(link, i) filename = f"{i:03d} - {safe_name(outbound['tag'])}.json" filepath = os.path.join(out_dir, filename) with open(filepath, "w", encoding="utf-8") as f: json.dump(outbound, f, ensure_ascii=False, indent=2) index_lines.append(f"{i:03d} -> {outbound['tag']}") with open(os.path.join(out_dir, "INDEX.txt"), "w", encoding="utf-8") as f: f.write("\n".join(index_lines)) print(f"Done. Files saved to: {out_dir}") if __name__ == "__main__": main()
Запуск:
python3 ~/Desktop/convert_subscription.py '<SUBSCRIPTION_URL>' open ~/Desktop/v2rayxs-outbounds
После этого на рабочем столе появится папка v2rayxs-outbounds с JSON-файлами.

Скрипт раскладывает подписку на отдельные JSON-файлы.

Каждый такой файл можно открывать, копировать код и вставлять в Advanced -> Outbounds
Что сработало
Сработало:
отказаться от Happ как от основного сценария для Catalina;
не переоценивать формальную совместимость IRBox с macOS 10.15;
использовать V2RayXS как GUI над Xray;
не доверять auto-import, если он создаёт пустой конфиг;
собирать VLESS + Reality вручную через
Advanced -> Outbounds;подключить Telegram Desktop отдельно через SOCKS5
127.0.0.1:1081;проверить диапазон Wi-Fi и перейти на 5 GHz.
Что не сработало
Не сработало:
ожидание, что «подписка импортировалась — значит всё готово»;
попытка работать с пустыми
VLESS-пунктами из кривого импорта;надежда, что браузер и Telegram поведут себя одинаково;
попытка использовать неподходящий клиент просто потому, что он хорошо работает на других устройствах.
Вывод
Главный вывод из этой истории простой: на старом Intel Mac с macOS Catalina рабочее подключение зависит не столько от формальной поддержки протокола VLESS/Reality, сколько от того, насколько конкретный клиент и его bundled core реально совместимы с системой. Happ сейчас требует macOS 12+ и для Catalina отпадает официально. IRBox формально поддерживает macOS 10.15+, но на практике оказалось что это не так. А V2RayXS оказался рабочим именно потому, что позволил обойти кривой импорт и вручную собрать нормальный Xray-конфиг.
SergeMNE
Предполагаю, что на старом Intel Mac с macOS Catalina выбор нормального vless клиента - это далеко не единственная проблема.
А почему не сразу не воспользоваться патчером и не накатить хотя бы Вентуру?
Вот репо с патчером https://github.com/dortania/OpenCore-Legacy-Patcher/releases
BugM
Есть старый ноут. Работает, музыку играет и видосики показывает. Больше от него ничего и не нужно.
Что-то накатывать это перебор. Хочется просто приложеньку чтобы работала.
PS: Старый мак у меня как раз есть. Попробую. Я уж думал что ютуб он отпоказывал.