Всё описанное ниже — результат технического эксперимента. Материал не является рекламой, не призывает к каким-либо действиям, носит исключительно ознакомительный характер и подготовлен в рамках исследования.

Основной источник информации репозиторий на GitHub

VPS сервер

VPS можно купить где угодно с возможность оплачивать всем чем только можно и даже криптой. В качестве рабочих примеров timeweb.cloud, hostmenow.org, regxa.com. Как итог на руках должны быть логин и пароль от сервера с Debian, на котором есть белый ip адрес. В качестве примера возьмем 10.10.10.10

Domain

Приобретаем доменное имя. Проще всего сделать это там же где и был куплен VPS. Если есть желание сэкономить, можно пойти на spaceship.com, а можно купить и на reg.ru, в общем варинтов тоже много, цена будет зависеть исключительно от того что будет выбрано. В качестве примера example-site.com

DNS

Там же где и купили домен, прописываем белый адрес в DNS записи, что бы весь мир узнал своих героев! Выглядить будет примерно вот так:

  1. record 1
  • Type - A (IPv4 address)
  • Name - example-site.com
  • Value - 10.10.10.10
  • TTL - выбрать самое минимальное значение, например “1 hour”
  1. record 2
  • Type - A (IPv4 address)
  • Name - www.example-site.com
  • Value - 10.10.10.10
  • TTL - выбрать самое минимальное значение, например “1 hour”

Некоторые провайдеры в полях Name хотят видеть иные записи:

После того как всё прописано надо ждать… Что бы узнать о распространение DNS записи переодически можно делать:

sudo dig example-site.com

В поле ANSWER должно быть НЕ 0.

nginx

Пока записи распространяются, можно установить и настроить nginx:

Обновляем репозитории и ставим пакет:

sudo apt update && sudo apt install nginx -y

Создаем рабочую папку для сайта:

sudo mkdir -p /var/www/example-site
sudo chown -R $USER:$USER /var/www/example-site

Создаем тестовую страницу:

sudo echo "<h1>[ THE EXAMPLE SITE: OPERATIONAL ]</h1>" > /var/www/example-site/index.html

Создаем файл конфигурации:

sudo mcedit /etc/nginx/sites-available/example-site

и добавляем в него

server {
    listen 80;
    server_name example-site.com www.example-site.com;

    root /var/www/example-site;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Создаем символическую ссылку и активируем:

sudo ln -s /etc/nginx/sites-available/example-site /etc/nginx/sites-enabled

Удаляем стандартный конфиг:

sudo rm /etc/nginx/sites-enabled/default
sudo rm /etc/nginx/sites-available/default

Проверяем систему на ошибки и перезапускаем:

sudo nginx -t
sudo systemctl restart nginx

Последний пункт “Золотое правило nginx” - создал/изменил, проверил, рестартанул

Рекомендуется оформить страницу сайта, как рабочую. Для этого можно попросить любую нейронку сгенирировать её

SSL

Когда у в поле ANSWER появился не 0, получаем сертификат. Для этого устанавливаем бота Let’s Encrypt, который будет заботиться об этом.

sudo apt update && sudo apt install certbot python3-certbot-nginx -y

Затем получаем сертификат (если в поле ANSWER 0, то сертификат получить не удастся):

sudo certbot --nginx -d example-site.com -d www.example-site.com
  1. Бот спросит email для уведомлений о продлении.
  2. Попросит согласиться с условиями.
  3. Cпросит, нужно ли делать Redirect (перенаправление с http на https) - выбираем ДА
  4. Бот перепишет конфиг Nginx и добавит туда защиту.

Сертификаты Let’s Encrypt живут 90 дней. Certbot создает задачу в планировщике для автопродления, проверить это можно командой:

sudo certbot renew --dry-run

В итоге заходя на сайт “example-site.com” с локальной машины подключение должно пройти по https и никто не должен ругаться.

Подготовка nginx

Чтобы и сайт, и Xray жили на 443 порту, нам понадобится реализовать fallback - откат при запросе сайта. Xray станет главным, он первым принимает входящие соединения на 443 порту и делает следующее:

  • если стучится VPN-клиент с правильным ключом — Xray его пропускает;

  • если заходит обычный пользователь или бот-сканер — Xray перекидывает запрос на локальный сайт.

Так как сайт сейчас слушает 443 порт, его необходимо перевести на другой порт (например 8443), который будет доступен только внутри сервера (localhost). Для этого правим конфигурацию, которую отредактировал бот Let’s Encrypt:

sudo mcedit /etc/nginx/sites-available/example-site

и меняем

server {
    server_name example-site.com www.example-site.com;

    root /var/www/example-site;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # BEFORE: listen 443 ssl; # managed by Certbot
    # AFTER:
    listen 127.0.0.1:8443 ssl;

    ssl_certificate /etc/letsencrypt/live/example-site.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example-site.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}

server {
    if ($host = www.example-site.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = example-site.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name example-site.come www.example-site.com;
    return 404; # managed by Certbot

}

После сохранения проверяем и рестартуем:

sudo nginx -t && sudo systemctl restart nginx

Стоит отметить важное замечание по безопасности: Необходимо убедитесь, что порт 8443 закрыт снаружи и доступен только для 127.0.0.1. Проверить можно с помощь:

sudo ss -tulpn

Вывод должен содержать запись

127.0.0.1:8443

больше порт 8443 “висеть” нигде не должен.

Xray

Установка Xray из официального скрипта:

sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Команда скачает последнюю версию Xray и создаст системную службу.

Генерация ключей и UUID

Для работы протокола Reality необходимо сгенирировать ключи:

sudo xray x25519

После выполнения команды получаем Private key, Password и Hash32. Их необходимо сохранить в блокнот. Приватный ключ пойдет в конфиг сервера, а пароль — в настройки клиента.

Также сгенерируем и сохраним UUID (пароль):

sudo xray uuid

и короткое имя сервера (shortId), естественно сохранить:

sudo openssl rand -hex 8

Настройка конфигурации

Когда есть все необходимые данные, требуется отредактировать конфигурационный файл Xray сервера:

sudo mcedit /usr/local/etc/xray/config.json

и прописать в нем следующее:

{
  "log": {
    "loglevel": "warning",
    "access": "/var/log/xray/access.log",
    "error": "/var/log/xray/error.log"
  },
  "inbounds": [
    {
      "listen": "IP_ADDRES",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "UUID",
            "flow": "xtls-rprx-vision",
            "level": 0,
            "email": "SOME-NAME"
          }
        ],
        "decryption": "none",
        "fallbacks": [
          {
            "dest": "127.0.0.1:8443"
          }
        ]
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "127.0.0.1:8443",
          "xver": 0,
          "serverNames": [
            "example-site.com",
            "www.example-site.com"
          ],
          "privateKey": "PRIVATE_KEY",
          "shortIds": [
            "SHORTID"
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    },
    {
      "tag": "block",
      "protocol": "blackhole"
    }
  ]
}

При этом на место “PRIVATE_KEY”, “UUID”, “SHORTID” вставить свои, ранее полученные значения, а в поле “IP_ADDRES” подставить белый ip адрес (это поле можно удалить, тогда будудет Xray будет слушать на всех интерфейсах). “SOME-NAME” - любое имя для дальнейшей идентификации пользователя.

После обновления конфигурации проверяем, что всё работает:

sudo xray -test -config /usr/local/etc/xray/config.json

Если есть сообщение “Configuration OK”, то рестартуем, если нет, то можно загнать конфигу в кокой-нибудь автоформатер, например сюда.

sudo systemctl restart xray && sudo systemctl status xray

Также проверим, что всё в порядке с портами - Xray должен занять 443 порт.

sudo ss -tulpn

После чего сайт должен снова быть доступен.

BBR

Bottleneck Bandwidth and Round-trip propagation time (BBR) — это алгоритм управления перегрузками для протокола TCP.

Редактируем репозитории

sudo mcedit /etc/apt/sources.list

Добавляем следующую строку в конец файла и сохраняем его:

deb http://archive.debian.org/debian buster-backports main

Обновляем список доступных пакетов и устанавливаем последюю версию

sudo apt update && sudo apt -t buster-backports install linux-image-amd64

Редактируем файл конфигурации sysctl.conf и включаем BBR

sudo mcedit /etc/sysctl.conf

Добавляем следующие строки в конец файла:

net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

Ребутаем VPS

sudo reboot

Для проверки работы bbr:

lsmod | grep bbr && lsmod | grep fq

Видим что-то вроде:

tcp_bbr  21450  90
sch_fq   21450  2

IPhone, Android, MacOS, Windows

Чтобы добавить еще одного пользователя достаточно добавить новый объект в массив “clients” внутри существующего конфига. Каждый клиент должен иметь свой уникальный UUID. Для этого генерируем новый UUID и добавляется в соотвествубщее поле. Xray поймет, кто именно подключился, по значению UUID в заголовке пакета.

Формирование ссылки

Ссылку можно делать руками, а можно и нельзя… Создаем файл скрипта:

sudo mcedit ./link_former.py

и вставляем код:

#! /bin/python3

import json
import uuid
import subprocess
import urllib.parse

def restart_xray():
    try:
        subprocess.run(["systemctl", "restart", "xray"], check=True)
        print("Xray service restarted successfully.")
    except subprocess.CalledProcessError:
        print("Error restarting Xray. Check permissions (sudo).")


def add_or_update_user():
    config_path = '/usr/local/etc/xray/config.json'
    username = input("Enter username: ")
    public_key = input("Enter Password: ")

    try:
        with open(config_path, 'r+') as f:
            config = json.load(f)
            inbound = config['inbounds'][0]
            clients = inbound['settings']['clients']

            # Search and overwrite
            existing_user = next((c for c in clients if c.get('email') == username), None)
            user_id = str(uuid.uuid4())

            if existing_user:
                existing_user['id'] = user_id
                print(f"\nUser '{username}' updated.")
            else:
                clients.append({"id": user_id, "flow": "xtls-rprx-vision", "email": username, "level": 0})
                print(f"\nUser '{username}' added.")

            f.seek(0)
            json.dump(config, f, indent=2)
            f.truncate()

            # Parameters from the config
            stream_settings = inbound['streamSettings']
            reality = stream_settings['realitySettings']

            # Build query parameters
            params = {
                "encryption": "none",
                "flow": "xtls-rprx-vision",
                "security": "reality",
                "sni": reality['serverNames'][0],
                "fp": "chrome",
                "pbk": public_key,
                "sid": reality['shortIds'][0],
                "type": "raw"
            }

            query_string = urllib.parse.urlencode(params)
            link = f"vless://{user_id}@{reality['serverNames'][0]}:{inbound['port']}?{query_string}#{username}"

            print(f"\nReady link:\n{link}\n")

    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    add_or_update_user()
    restart_xray()
    

Даем права на испольнения

sudo chmod 755 ./link_former.py

После запуска

./link_former.py

Скрипт попросит ввести:

  • новое имя пользователя - вводим то что нравится
  • пароль, который получили при генерации ключей

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

v2Box (Android, IPhone, MacOS)

Для работы на мобильных телефонах IPhone и компьютерах MacOS используется приложение v2Box, для Android - v2rayNG. Эти приложения пробросывают весь трафик на ваш Xray сервер.

Что бы подключиться необходимо:

  • установить v2Box;
  • скопировать полученную конфигурационную ссылку;
  • в приложении перейти в пункт “Конфигурации”;
  • нажать плюс (+) и выбрать “Импортировать v2ray URI из буфера”.

v2rayN (Windows)

Для Windows есть разные варианты c графической оболочкой, один из самых популярных v2rayN:

  • переходим в официальный репозиторий;
  • скачиваем релищ под необходимую архитектуру и распаковывем;
  • запускаем исполняемый файл “v2rayN.exe” от имени администратора;
  • заходим в пункт “Configuration”;
  • выбираем “Import Share link from clipboard”;
  • подтверждаем “Confirm”;
  • в нижней части окна программы переводим бегунок “Enable Tun” во включенное положение;
  • в окне “System proxy” (справа от Enable Tun) выбираем “Set system proxy”
  • в окне “Routing” (справа от System proxy) выбираем “V4-Global”

Linux (cli)

Сначала требуется сформировать конфигурацию и затем уже на её основе использовать клиентов Xray. Файл с настройками клиента можно сформировать руками, а можно не руками:

sudo mcedit ./json_former.py

и вставляем код:

#! /bin/python3

import json
import uuid
import subprocess

def restart_xray():
    try:
        subprocess.run(["systemctl", "restart", "xray"], check=True)
        print("Xray service restarted successfully.")
    except subprocess.CalledProcessError:
        print("Error restarting Xray. Check permissions (sudo).")

def add_or_update_user():
    config_path = '/usr/local/etc/xray/config.json'
    username = input("Enter username: ")
    public_key = input("Enter Password: ")

    try:
        with open(config_path, 'r+') as f:
            config = json.load(f)
            inbound = config['inbounds'][0] # Get the first inbound
            clients = inbound['settings']['clients']

            # Search and overwrite
            existing_user = next((c for c in clients if c.get('email') == username), None)
            user_id = str(uuid.uuid4())

            if existing_user:
                existing_user['id'] = user_id
                print(f"\nUser '{username}' updated.")
            else:
                clients.append({"id": user_id, "flow": "xtls-rprx-vision", "email": username, "level": 0})
                print(f"\nUser '{username}' added.")

            f.seek(0)
            json.dump(config, f, indent=2, ensure_ascii=False)
            f.truncate()

             # Parameters from the config
            stream_settings = inbound['streamSettings']
            reality = stream_settings['realitySettings']

            client_config = {
                'log': {
                    'loglevel': 'warning'
                },
                'dns': {
                    "hosts": {
                        reality['serverNames'][0]: inbound['listen']
                    },
                    "servers": [
                        "8.8.8.8",
                        "1.1.1.1",
                        "8.8.4.4"
                    ],
                },
                "inbounds": [
                    {
                        "tag": "socks",
                        "port": 10808,
                        "listen": "127.0.0.1",
                        "protocol": "socks",
                        "sniffing": {
                            "enabled": True,
                            "destOverride": ["http", "tls", "quic"],
                            "routeOnly": False
                        },
                        "settings": {
                            "auth": "noauth",
                            "udp": True
                        }
                    }
                ],
                "outbounds": [
                    {   "tag": "proxy",
                        "protocol": "vless",
                        "settings": {
                            "address": reality['serverNames'][0],
                            "port": inbound['port'],
                            "id": user_id,
                            "encryption": "none",
                            "flow": "xtls-rprx-vision",
                            "level": 0
                        },
                        "streamSettings": {
                            "network": "raw",
                            "security": "reality",
                            "realitySettings": {
                                "serverName": reality['serverNames'][0],
                                "fingerprint": "chrome",
                                "show": False,
                                "publicKey": public_key,
                                "shortId": reality['shortIds'][0]
                            }
                        }
                    },
                    {
                    "tag": "dns-out",
                    "protocol": "dns",
                    "settings": {
                            "network": "tcp,udp",
                            "nonIPQuery": "drop"
                        }
                    },
                    {
                    "tag": "direct",
                    "protocol": "freedom"
                    },
                    {
                    "tag": "block",
                    "protocol": "blackhole"
                    }
                ],
                "routing": {
                    "domainStrategy": "IPIfNonMatch",
                    "rules": [
                        {
                            "type": "field",
                            "inboundTag": ["socks"],
                            "port": 53,
                            "network": "tcp,udp",
                            "outboundTag": "dns-out"
                        },
                        {
                            "type": "field",
                            "inboundTag": ["socks"],
                            "outboundTag": "proxy",
                            "network": "tcp,udp"
                        }
                    ]
                }
            }

            client_filename = f"{username}_client.json"
            with open(client_filename, 'w') as cf:
                json.dump(client_config, cf, indent=2, ensure_ascii=False)

            print(f"\nConfiguration saved to file: {client_filename}")

    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    add_or_update_user()
    restart_xray()

Даем права на испольнения

sudo chmod 755 ./json_former.py

И запускаем. Смысл такой же как и в “link_former.py”

sudo ./json_former.py

Для Linux существуют версии и с графической оболочкой, например тот же v2rayN, но для консольной версии самый простой способ — использовать само ядро Xray-core.

Установка Xray официальным скриптом:

sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Проверяем конфигурацию и заодно сам Xray:

sudo xray -test -config NEW_CLIEN_CONFIG.json

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

sudo wget https://github.com/tun2proxy/tun2proxy/releases/latest/download/tun2proxy-x86_64-unknown-linux-gnu.zip

Распаковываем и даём права:

sudo unzip ./tun2proxy-x86_64-unknown-linux-gnu.zip && chmod +x tun2proxy-bin

Перемещаем и проверяем что всё готово:

sudo mv tun2proxy-bin /usr/local/bin/tun2proxy && tun2proxy --version

Далее запускаем Xray:

sudo xray run -c client_config.json

и в соседнем окне tun2proxy:

sudo tun2proxy --setup --proxy socks5://127.0.0.1:10808

На место [IP_ADDRESS] необходимо подставить белый ip сервера.

Следует оговориться, что этот трюк можно провернуть и иным способом, не через socks, а transparent proxy (tproxy), но тогда не следует забывать и про маршрутизацию, в лучше случае nftables, а где-то и вовсе iptables. По этому socks…

Управление маршрутами и блокировками

Что бы более гибкого управлять трафиком (например что-то слать через Vray, а что-то напрямую через провайдера), можно исправить конфигурацию и добавить дополнительные правила - “rules” в блоке “routing”.

Для блокировки рекламы, аналитики и исключения работы с уязвимыми протоколами используются блоки которые отправляют весь трафик по тегу “block” из конфигурации тем самым блокируя его:


{
"_note": "Vulnerable prots",
"type": "field",
"inboundTag": ["socks-in"],
"outboundTag": "block",
"network": "udp",
"port": "135,137,138,139"
},
{
"_note": "Adds blocking",
"type": "field",
"inboundTag": ["socks-in"],
"domain": ["geosite.dat:category-ads-all"],
"outboundTag": "block"
}

“Specific IP address” и “Block ads”. Вставлять дополнительные настройки необходимо по такому же принципу, как располагаются уже существующие. Следует выделить два подхода:

  • Автоматический
{
"_note": "Domain names",
"type": "field",
"inboundTag": ["socks-in"],
"domain": ["geosite:private", "geosite:category-ru"],
"outboundTag": "direct"
},
{ 
"_note": "IP addresses",
"type": "field",
"inboundTag": ["socks-in"],
"ip": ["geoip:private", "geoip:ru"],
"outboundTag": "direct"
}
  1. geosite:private - БД с доменными именами, из которой взяты локальные домены (localhost, .local, .lan)
  2. geosite:category-ru - БД с доменными именами, из которой взяты популярные домены в зоне .ru, .su, .рф
  3. geoip:private - БД с ip адресами, из которой взяты диапазоны «серых» IP-адресов (192.168.x.x, 10.x.x.x, 127.0.0.1)
  4. geoip:ru - БД с ip адресами из которой взяты все диапазоны IP-адресов, зарегистрированные за российскими провайдерами

Таким образом, применя эти настройки часть трафика пойдет без vray - direct. Остальная часть, отправится через Vray.

  • Ручной
{ 
    "_note": "Manual proxing",
    "type": "field",
    "domain": [
        "domain:2ip.io",
        "domain:youtube.com"
    ],
    "outboundTag": "vless-reality"
},
{ 
    "_note": "Manual directing",
    "type": "field",
    "domain": [
        "domain:yandex.ru",
        "domain:vk.ru"
    ],
    "outboundTag": "direct"
}
  1. direct - отправить без Vray
  2. proxy - отправить через Vray

Применя эти настройки часть трафика пойдет без vray - direct. Остальная часть, отправится через Vray - proxy.

Ознакомиться со списком доменов, ip адресами, обновить базу и узнать про ещё более гибкие настроки можно в репозитории на GitHub.