user@elrise.ru:~
2026-07-2830 min readvpnhysteriaxrayvlessrealityquictlssysadminnetworking

TLS-маскировка в Hysteria 2 и XTLS-Reality: развёртывание прокси на собственном VPS

0. Что это и зачем

Материал разбирает архитектуру и развёртывание двух современных прокси-протоколов с TLS-маскировкой — Hysteria 2 и XRay с XTLS-Reality — на собственном VPS (Debian 12 / Ubuntu 22.04+):

Оба протокола интересны с инженерной точки зрения как пример того, как QUIC, ACME, x25519-ключи и systemd собираются в готовый к продакшену стек. Развёртывание занимает ~20 минут на один протокол.

Гайд предполагает, что у тебя уже есть:


1. Предусловия

1.1. Обновить систему и поставить базовые пакеты

apt update && apt -y upgrade
apt -y install curl wget tar socat cron nginx-light dnsutils

1.2. Открыть порты в firewall

UFW (Ubuntu):

ufw allow 22/tcp comment 'ssh'
ufw allow 80/tcp comment 'acme + http redirect'
ufw allow 443/tcp comment 'vless reality'
ufw allow 443/udp comment 'hysteria2 quic'
ufw --force enable
ufw status numbered

iptables-nft (Debian 12 default):

apt -y install iptables-persistent
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p udp --dport 443 -j ACCEPT
netfilter-persistent save

1.3. Получить LE-сертификат через acme.sh

curl -fsSL https://get.acme.sh | sh -s email="<USER_EMAIL>"
source ~/.bashrc
mkdir -p /etc/hysteria/certs
~/.acme.sh/acme.sh --issue -d <SERVER_DOMAIN> --standalone --httpport 80 --server letsencrypt
~/.acme.sh/acme.sh --install-cert -d <SERVER_DOMAIN> \
    --cert-file      /etc/hysteria/certs/server.crt \
    --key-file       /etc/hysteria/certs/server.key \
    --fullchain-file /etc/hysteria/certs/server.fullchain.pem \
    --reloadcmd      "systemctl reload hysteria-server.service 2>/dev/null || true"

<SERVER_DOMAIN> — твой домен, например vpn.example.com. <USER_EMAIL> — почта для регистрации ACME-аккаунта.

Проверить, что сертификат валидный:

openssl x509 -in /etc/hysteria/certs/server.crt -noout -subject -issuer -dates

Ожидаемый issuer: O = Let's Encrypt. Сертификат валиден 90 дней, acme.sh автоматически его обновляет раз в ~60 дней через cron.


2. Hysteria 2

2.1. Установка

Актуальный релиз: https://github.com/apernet/hysteria/releases

HY_VER="2.6.0"   # проверь последнюю версию на GitHub
cd /tmp
wget -q "https://github.com/apernet/hysteria/releases/download/app/v${HY_VER}/hysteria-linux-amd64" \
     -O /usr/local/bin/hysteria
chmod +x /usr/local/bin/hysteria
hysteria version

2.2. Конфиг

Создать /etc/hysteria/config.yaml:

log:
  level: info

listen: 0.0.0.0:8443

tls:
  cert: /etc/hysteria/certs/server.crt
  key:  /etc/hysteria/certs/server.key

auth:
  type: password
  password: "<HY2_PASSWORD>"   # замени на случайную 24-символьную строку

masquerade:
  type: proxy
  proxy:
    url: https://www.yandex.ru/
    rewriteHost: true

disableUDP: false

Сгенерировать пароль:

openssl rand -base64 24 | tr -d '+/=' | head -c 24

Параметр masquerade.url — это сайт, который Hysteria покажет при active probing (если кто-то подключится к UDP 8443 без правильного пароля). Российский сайт в белых списках ТСПУ предпочтительнее, чем зарубежный.

2.3. systemd-unit

Создать /etc/systemd/system/hysteria-server.service:

[Unit]
Description=Hysteria 2 Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
WorkingDirectory=/etc/hysteria
ExecStart=/usr/local/bin/hysteria server --config /etc/hysteria/config.yaml
Restart=on-failure
RestartSec=5s
LimitNOFILE=65535
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Активировать:

systemctl daemon-reload
systemctl enable --now hysteria-server.service
systemctl status hysteria-server.service

Ожидаемый вывод: Active: active (running). В логе:

server up and running {"listen": "0.0.0.0:8443"}

2.4. Share-ссылка для клиента

DOMAIN=<SERVER_DOMAIN>
PASS="<HY2_PASSWORD>"

printf "hysteria2://%s@%s:8443/?sni=%s&insecure=0&obfs=none&protocol=udp#hysteria-%s\n" \
       "$PASS" "$DOMAIN" "$DOMAIN" "$DOMAIN"

Или через IP, если домен ещё не прописался в DNS у клиента:

printf "hysteria2://%s@<SERVER_IP>:8443/?sni=%s&insecure=0&obfs=none&protocol=udp#hysteria-%s\n" \
       "$PASS" "$DOMAIN" "$DOMAIN"

2.5. Проверка

С локальной машины:

ss -ulnp | grep 8443          # должен слушать Hysteria
ss -tlnp | grep 8443          # ничего не должно быть (Hysteria = UDP)

# С рабочего стола (через VPN или с другой геолокации):
curl -k https://<SERVER_DOMAIN>:8443/   # должна вернуться 400 или страница yandex (active probing)

Через клиент на Android — подключение должно подняться за <1 секунды.


3. XRay + VLESS+Reality

3.1. Установка XRay

Актуальный релиз: https://github.com/XTLS/Xray-core/releases

XR_VER="26.1.13"   # проверь на GitHub
wget -q "https://github.com/XTLS/Xray-core/releases/download/v${XR_VER}/Xray-linux-64.zip" \
     -O /tmp/xray.zip
apt -y install unzip
unzip -o /tmp/xray.zip -d /usr/local/bin/
chmod +x /usr/local/bin/xray
/usr/local/bin/xray version

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

# x25519 ключи для Reality
/root/bin/xray-keygen 2>/dev/null   # placeholder, реально:
/usr/local/bin/xray x25519

# Запиши:
#   PrivateKey: ...
#   Password (PublicKey): ...

# UUID для клиента
/usr/local/bin/xray uuid
# Запиши: например 00000000-0000-0000-0000-000000000000

# ShortID (любые случайные 16 hex символов)
openssl rand -hex 8
# Запиши: например abcdef0123456789

3.3. Конфиг

Создать /usr/local/etc/xray/config.json:

{
  "log": {
    "loglevel": "warning",
    "access": "/var/log/xray/access.log",
    "error":  "/var/log/xray/error.log"
  },
  "inbounds": [
    {
      "tag": "vless-reality-443",
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "decryption": "none",
        "clients": [
          {
            "id": "<XR_UUID>",
            "flow": "xtls-rprx-vision",
            "email": "main-client"
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "www.yandex.ru:443",
          "xver": 0,
          "serverNames": [
            "rutube.ru",
            "smartcaptcha.yandexcloud.net",
            "www.yandex.ru",
            "yandex.ru"
          ],
          "privateKey": "<XR_PRIVKEY>",
          "shortIds": [
            "<XR_SHORTID>"
          ]
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": ["http", "tls", "quic"],
        "routeOnly": true
      }
    }
  ],
  "outbounds": [
    { "protocol": "freedom", "tag": "direct" },
    { "protocol": "blackhole", "tag": "block" }
  ]
}

Параметры:

3.4. systemd-unit

Создать /etc/systemd/system/xray.service:

[Unit]
Description=Xray Service
Documentation=https://github.com/XTLS/xray-core
After=network-online.target nss-lookup.target
Wants=network-online.target nss-lookup.target

[Service]
Type=simple
User=nobody
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json
Restart=on-failure
RestartPreventExitStatus=23
LimitNPROC=65535
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

Активировать:

mkdir -p /var/log/xray
useradd -r -s /usr/sbin/nologin nobody 2>/dev/null || true
chown -R nobody:nogroup /var/log/xray
systemctl daemon-reload
systemctl enable --now xray.service
systemctl status xray.service

3.5. Освобождение порта 443 (если занят nginx)

Reality должна слушать на 443, чтобы выглядеть как обычный HTTPS. Если на сервере уже работает nginx, его надо перевесить на другой порт (8443 или 8080).

# В конфигах nginx найти listen 443 ssl и поменять на 8443 ssl
grep -rn "listen.*443" /etc/nginx/

# После правки:
nginx -t && systemctl reload nginx

3.6. Share-ссылка для клиента

PUB="<XR_PUBKEY>"      # Password из шага 3.2
SID="<XR_SHORTID>"     # из шага 3.2
UUID="<XR_UUID>"
SERVER="<SERVER_IP>"   # или <SERVER_DOMAIN>

# Основная ссылка (SNI = yandex.ru, обычный DPI)
printf "vless://%s@%s:443?type=tcp&security=reality&pbk=%s&fp=chrome&sni=www.yandex.ru&sid=%s&flow=xtls-rprx-vision#main-yandex\n" \
       "$UUID" "$SERVER" "$PUB" "$SID"

# Для белых списков (SNI = rutube.ru)
printf "vless://%s@%s:443?type=tcp&security=reality&pbk=%s&fp=chrome&sni=rutube.ru&sid=%s&flow=xtls-rprx-vision#main-rutube\n" \
       "$UUID" "$SERVER" "$PUB" "$SID"

# Альтернатива (SNI = smartcaptcha)
printf "vless://%s@%s:443?type=tcp&security=reality&pbk=%s&fp=chrome&sni=smartcaptcha.yandexcloud.net&sid=%s&flow=xtls-rprx-vision#main-smartcaptcha\n" \
       "$UUID" "$SERVER" "$PUB" "$SID"

3.7. Проверка

ss -tlnp | grep 443          # должен слушать xray

# Active probing test: Reality должен вернуть сертификат dest-сайта
timeout 10 openssl s_client -connect <SERVER_IP>:443 -servername rutube.ru -brief 2>&1 | head -10

Ожидаемый вывод: Peer certificate: C = RU, O = YANDEX LLC, CN = *.yandex.tr, Verification: OK. Если вывод пустой или ошибка — Reality не работает.


4. Клиент на Android

4.1. Рекомендуемые клиенты

4.2. Импорт share-ссылки в Hiddify

  1. Установить Hiddify Next из GitHub Releases (.apk).
  2. Открыть → «+» в правом верхнем углу → «Добавить из буфера обмена».
  3. Скопировать share-ссылку (из шага 2.4 или 3.6) в буфер обмена.
  4. Нажать на добавленное — Hiddify покажет карточку профиля.
  5. В настройках профиля проверить параметры:
    • для Hysteria 2: адрес, порт, пароль, SNI.
    • для VLESS+Reality: адрес, порт, UUID, flow=xtls-rprx-vision, pbk, sid, sni. Если поле «SNI» редактируется — оно совпадает с sni= в share-link.
  6. В главном экране нажать большую кнопку «Подключиться» — статус изменится на «Подключено», появится уведомление с VPN-значком.

4.3. Режимы DPI и белых списков

В нормальном режиме DPI ТСПУ работает с обычным DPI-анализом трафика. В режиме белых списков (включается регионально при БПЛА-угрозах) — трафик пропускается только к «белым» доменам. В этом режиме:

Клиент Hiddify умеет автоматически переключать профили по правилам, но проще вручную: если обычный Reality не подключается — переключиться на профиль с SNI=rutube.ru.

4.4. Авто-выбор ближайшего сервера

Если у тебя несколько VPN-серверов на разных IP, в Hiddify можно включить «авто-пинг»:

Hiddify будет пробовать все профили и подключаться к самому быстрому.


5. Технические детали

5.1. Почему VLESS+Reality устойчив к DPI

Reality не использует собственный TLS-сертификат. На этапе TLS-handshake клиент отправляет ClientHello с SNI=yandex.ru (или другой из serverNames). Сервер Reality отдаёт сертификат dest-сайта на лету — то есть тот же сертификат, что выдал бы реальный yandex.ru. Passive DPI видит «клиент подключается к yandex.ru, получает валидный сертификат yandex» — стандартный паттерн.

Active probe (когда РКН сам подключается к нашему IP:443): если у него нет правильного PublicKey и ShortID, Reality проксирует TCP-соединение на dest = yandex.ru:443 и тот отдаёт нормальный ответ. Проверяющий не может отличить наш сервер от настоящего yandex.ru.

5.2. Почему Hysteria 2 работает

Hysteria использует QUIC (UDP). QUIC — относительно новая технология, и многие DPI не умеют глубоко его анализировать. Сертификат валидный (LE), fingerprint обычного QUIC-клиента. DPI видит «UDP 443 с QUIC-handshake и валидным TLS» — пропускает. У Hysteria есть дополнительные obfuscation-плагины, но базовый режим с ACME-cert работает на Т-Мобайл в большинстве регионов.

5.3. Что НЕ работает на Т-Мобайл

Это для контекста, чтобы не терять время на настройку:

5.4. Что такое «белые списки» ТСПУ

В отдельные периоды (БПЛА-угрозы, учения) ТСПУ переключается в режим, когда пропускается трафик только к сайтам из утверждённого списка: российские госсайты, банки, маркетплейсы, видеосервисы. В этом режиме:

Решение для белых списков — единственный известный трюк: SNI в Reality должен быть из белого списка. Российские домены, стабильно попадающие в белые списки:


6. Управление сервисами

# Hysteria 2
systemctl status hysteria-server.service
systemctl restart hysteria-server.service
journalctl -u hysteria-server.service -n 50 --no-pager

# XRay
systemctl status xray.service
systemctl restart xray.service
journalctl -u xray.service -n 50 --no-pager

# Логи XRay (доступ / ошибки)
tail -f /var/log/xray/access.log
tail -f /var/log/xray/error.log

# Проверка сертификата (не истёк ли)
openssl x509 -in /etc/hysteria/certs/server.crt -noout -dates

7. Troubleshooting

7.1. Hysteria: клиент подключается, но нет интернета

Проверить, что DNS работает через VPN:

# На сервере
tcpdump -i any -n udp port 53 | head -20
# На клиенте открыть https://1.1.1.1 — должен показать страницу Cloudflare

Если DNS не работает — добавить в конфиг Hysteria секцию:

tcpForwarding:
  enabled: true
udpForwarding:
  enabled: true

7.2. Reality: клиент получает ошибку invalid request

7.3. Reality: сертификат CN показывает чужой сайт (например yandex.tr вместо yandex.ru)

Это нормально. Yandex выдаёт wildcard *.yandex.tr для некоторых регионов. Reality берёт этот сертификат на лету с dest-сайта и не подделывает его. Клиент не получает ошибку, потому что мы проходим TLS-handshake через Reality, а не как обычный HTTPS-клиент.

7.4. Active probing со стороны РКН

Если на сервере Reality, а проверяющий видит сертификат yandex.ru — это ожидаемо и безопасно. Проверяющий получит настоящий сайт yandex при подключении без правильных ключей. Это и есть защита от active probing.

7.5. Сертификат Hysteria истёк

acme.sh обновляет автоматически. Если нет — перезапустить вручную:

~/.acme.sh/acme.sh --renew -d <SERVER_DOMAIN> --force
~/.acme.sh/acme.sh --install-cert -d <SERVER_DOMAIN> \
    --cert-file      /etc/hysteria/certs/server.crt \
    --key-file       /etc/hysteria/certs/server.key \
    --fullchain-file /etc/hysteria/certs/server.fullchain.pem
systemctl restart hysteria-server.service

7.6. Подключение клиента работает, но сайты не открываются

Проверить на сервере:

# Должен вернуть ответ
curl -I https://www.google.com
# Должен вернуть ответ от нашего IP
curl --interface eth0 ifconfig.me
# XRay/Hysteria логи
journalctl -u xray.service -n 20 --no-pager
journalctl -u hysteria-server.service -n 20 --no-pager

Если curl -I https://www.google.com не работает с самого сервера — проблема не в VPN, а в сетевой связности сервера.

7.7. Клиент Hiddify не подключается, но ссылка правильная


8. Hardening и best practices

8.1. Ротация паролей / UUID / ключей

8.2. Минимизация поверхности атаки

В конфиге XRay:

"policy": {
  "levels": {
    "0": {
      "handshake": 3,
      "connIdle": 300,
      "uplinkOnly": 2,
      "downlinkOnly": 5
    }
  },
  "system": {
    "statsInboundUplink": false,
    "statsInboundDownlink": false
  }
}

Ограничивает число handshake-ов и idle-соединения, чтобы усложнить активное сканирование.

8.3. Логи и аудит

XRay по умолчанию логирует warning и выше — access.log пишется только для ошибок. Не включать info на постоянной основе — логи могут расти.

logrotate -f /etc/logrotate.conf

8.4. Мониторинг

Минимальный health-check через systemd timer:

cat > /etc/systemd/system/vpn-healthcheck.timer << 'EOF'
[Unit]
Description=VPN health check timer

[Timer]
OnBootSec=5min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target
EOF

cat > /etc/systemd/system/vpn-healthcheck.service << 'EOF'
[Unit]
Description=VPN health check

[Service]
Type=oneshot
ExecStart=/usr/local/bin/vpn-healthcheck.sh
EOF

cat > /usr/local/bin/vpn-healthcheck.sh << 'EOF'
#!/bin/bash
set -e
ss -tlnp | grep -q ':443 .*xray'        || { echo "XRay down"; exit 1; }
ss -ulnp | grep -q ':8443 .*hysteria'  || { echo "Hysteria down"; exit 1; }
curl -fsSI https://www.google.com -m 5 > /dev/null || { echo "No internet"; exit 1; }
echo OK
EOF
chmod +x /usr/local/bin/vpn-healthcheck.sh

systemctl daemon-reload
systemctl enable --now vpn-healthcheck.timer

8.5. Бэкап конфигов

mkdir -p /root/vpn-config-backups
cd /etc && tar czf /root/vpn-config-backups/hysteria-$(date +%F).tar.gz hysteria
cd /usr/local/etc && tar czf /root/vpn-config-backups/xray-$(date +%F).tar.gz xray

9. Что почитать дальше


См. также

Дисклеймер

Использовать VPN — ай-ай-ай, не надо это вам. Заметка выше — про то, как поднять собственный VPN, и одновременно оговорка, что без веской причины этого делать не стоит: VPS за деньги, поддержка сертификатов, риск утечки конфигов и юридически серая зона. Если нужно просто читать новости или работать с кодом — прямого доступа хватит. Если всё-таки надо — гайд выше, но ответственность ваша.

Актуальность. Вся информация собрана из публичных источников (GitHub, официальные документации, открытые обзоры). Все утверждения могут уже таковыми не являться в любой момент времени: ТСПУ/DPI эволюционируют, протоколы обновляются, реестр РКН пополняется. Перед развёртыванием сверяйтесь с актуальной версией источников — ссылки собраны в разделе «Что почитать дальше» выше.

References

Обсуждение

Комментарии (0)

Пока никто не комментировал.