example.com + 3x-ui + VLESS REALITY Self-SNI

19 min read

README — example.com + api.example.com + 3x-ui + VLESS REALITY + XHTTP

Автор: @Yazykow

Этот документ описывает итоговую конфигурацию сервера после развёртывания сайта, 3x-ui и VLESS REALITY с Self-SNI.

Цели конфигурации:

  • обычный HTTPS-сайт продолжает работать на стандартном TCP-порту 443;
  • тот же 443/tcp используется VLESS REALITY;
  • обычный TLS-клиент или сканер получает настоящий сайт с настоящим TLS-сертификатом;
  • внутренний nginx поддерживает HTTP/2;
  • Xray передаёт nginx реальный IP клиента через PROXY protocol v1;
  • nginx передаёт реальный IP дальше приложению;
  • 3x-ui panel не доступна из внешней сети и открывается только через SSH port forwarding;
  • Xray core хранится отдельно от файлов панели и его версия фиксируется независимо от обновления 3x-ui;
  • сертификаты Let's Encrypt продолжают автоматически обновляться через HTTP-01 на порту 80;
  • отдельный subscription endpoint работает через api.example.com;
  • отдельный VLESS XHTTP packet-up работает на публичном 8444/tcp, не затрагивая основной REALITY на 443.

В документе намеренно отсутствуют реальные пароли, Web Base Path панели, UUID клиентов, REALITY private/public keys, Short ID, Subscription ID, XHTTP private path, реальные IP-адреса сервера и клиентские URL/URI.


1. Итоговая архитектура

                         INTERNET
                             |
                             | TCP/443
                             v
                     Xray / REALITY
                      VLESS inbound
                        xver = 1
                       /         \
                      /           \
          REALITY client          обычный TLS client
                 |                       |
                 |                       |
                 v                       |
            VLESS tunnel                 |
                                         |
                                 PROXY protocol v1
                                         |
                                         v
                              127.0.0.1:8443
                                     nginx
                                  TLS 1.2/1.3
                                    HTTP/2
                                         |
                                         |
                              X-Real-IP / XFF
                                         |
                                         v
                              127.0.0.1:18080
                                         |
                                         v
                              Docker / Django
                                         |
                                         v
                                   PostgreSQL

HTTP обслуживается отдельно:

Internet :80
    |
    v
  nginx
    |
    +--> /.well-known/acme-challenge/
    |       |
    |       +--> Let's Encrypt HTTP-01
    |
    +--> остальные запросы
            |
            +--> redirect на HTTPS

Панель 3x-ui:

Internet
   |
   X   прямого доступа нет
   |
3x-ui panel


локальный компьютер
        |
        | SSH port forwarding
        v
сервер 127.0.0.1:25572
        |
        v
     3x-ui

2. Основные порты

Ожидаемое состояние:

Порт Bind Процесс Назначение
SSH public sshd администрирование только по SSH key
80/tcp public nginx redirect + Let's Encrypt HTTP-01
443/tcp public Xray основной VLESS REALITY RAW/TCP + Self-SNI
8444/tcp public Xray отдельный VLESS XHTTP packet-up + REALITY
8443/tcp 127.0.0.1 nginx внутренний HTTPS Self-SNI backend
18080/tcp 127.0.0.1 Docker Django application
25572/tcp 127.0.0.1 x-ui 3x-ui web panel
2096/tcp 127.0.0.1 x-ui 3x-ui Subscription Server

Проверка:

sudo ss -lntp | grep -E ':(80|443|8444|8443|18080|25572|2096)?'

Ожидается концептуально:

0.0.0.0:80          nginx
[::]:80             nginx

*:443               Xray
*:8444              Xray

127.0.0.1:8443      nginx
127.0.0.1:18080     docker-proxy
127.0.0.1:25572      x-ui
127.0.0.1:2096      x-ui

Публичными являются только SSH, 80, 443 и 8444.

8443, 18080, 25572, 2096 наружу не открываются.


3. Сайт

Проект развёрнут как Docker Compose stack:

/opt/stacks/yazykow/

Основные команды:

cd /opt/stacks/yazykow

docker compose ps
docker compose logs --tail=100
docker compose restart

Проверка приложения напрямую:

curl http://127.0.0.1:18080/healthz

Ожидается:

ok

3.1. Проверка Docker

cd /opt/stacks/yazykow
docker compose ps

Контейнеры должны быть в состоянии running / healthy.

Логи:

docker compose logs --tail=200

Логи конкретного сервиса:

docker compose logs --tail=200 web
docker compose logs --tail=200 db
docker compose logs --tail=200 nginx

4. Обновление сайта

Перед каждым обновлением рекомендуется сделать backup базы.

cd /opt/stacks/yazykow

mkdir -p backup
chmod 700 backup

docker compose exec -T db sh -c \
  'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
  > "backup/postgres-$(date +%Y%m%d-%H%M%S).dump"

Проверить:

ls -lh backup/

После загрузки новой версии:

cd /opt/stacks/yazykow

docker compose config --quiet
docker compose build --pull
docker compose up -d
docker compose ps
docker compose logs --tail=100

После обновления:

curl http://127.0.0.1:18080/healthz

И внешний HTTPS health check:

curl -I https://example.com

Важно

Без необходимости нельзя использовать:

docker compose down -v

Флаг -v удаляет Docker volumes и может удалить PostgreSQL-данные.


5. Публичный nginx — только порт 80

Файл:

/etc/nginx/sites-available/example.com

Он не должен слушать публичный 443, поскольку 443 принадлежит Xray.

Итоговый конфиг:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type text/plain;
    }

    location / {
        return 301 https://$host$request_uri;
    }

    access_log /var/log/nginx/yazykow-access.log;
    error_log  /var/log/nginx/yazykow-error.log;
}

Проверить активные listen:

sudo nginx -T 2>/dev/null | grep -nE 'listen .*443'

Публичного:

listen 443 ssl;

в обычном site-конфиге быть не должно.


6. Внутренний nginx на 8443

Это ключевая часть Self-SNI.

Файл:

/etc/nginx/sites-available/yazykow-reality-backend

Symlink:

/etc/nginx/sites-enabled/yazykow-reality-backend

Финальный конфиг:

server {
    listen 127.0.0.1:8443 ssl proxy_protocol;
    http2 on;

    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

    # Xray подключается к backend локально.
    # При xver=1 он передаёт исходный IP клиента
    # в PROXY protocol v1.
    real_ip_header proxy_protocol;
    set_real_ip_from 127.0.0.1;

    client_max_body_size 16m;

    location / {
        proxy_pass http://127.0.0.1:18080;

        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_set_header Host $host;

        # После real_ip_header $remote_addr содержит
        # IP исходного клиента, а не 127.0.0.1.
        proxy_set_header X-Real-IP $remote_addr;

        # Не доверяем произвольному X-Forwarded-For,
        # присланному самим клиентом.
        proxy_set_header X-Forwarded-For $remote_addr;

        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port 443;
    }

    access_log /var/log/nginx/yazykow-reality-access.log;
    error_log  /var/log/nginx/yazykow-reality-error.log;
}

Применение:

sudo nginx -t
sudo systemctl reload nginx

7. Почему включён HTTP/2

Внутренний nginx — это TLS target REALITY.

Обычный TLS-клиент, который не является REALITY-клиентом, в итоге попадает именно сюда.

Поэтому target должен вести себя как обычный современный HTTPS-сайт:

TLS 1.2
TLS 1.3
ALPN h2
ALPN http/1.1
настоящий сертификат
настоящий сайт

HTTP/2 включается:

http2 on;

Проверить поддержку nginx:

sudo nginx -V 2>&1 | grep -oE 'http_v2_module|http_realip_module'

Желательно наличие:

http_v2_module
http_realip_module

8. PROXY protocol и реальный IP

При xver=0 Xray подключается к nginx с localhost, поэтому nginx видит:

127.0.0.1

При xver=1 Xray передаёт исходный IP и порт клиента через PROXY protocol v1.

Поэтому обе стороны должны быть настроены согласованно:

Xray:
Xver = 1

nginx:
listen 127.0.0.1:8443 ssl proxy_protocol;
real_ip_header proxy_protocol;
set_real_ip_from 127.0.0.1;

Связка:

клиент
  |
  v
Xray :443
  |
  | PROXY protocol v1
  | source=<реальный IP клиента>
  v
nginx :8443
  |
  | real_ip_header proxy_protocol
  v
$remote_addr = реальный IP клиента

После этого nginx передаёт IP приложению:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;

9. Важное правило xver / proxy_protocol

Эти две настройки нельзя менять независимо.

Рабочие комбинации:

Xver 0
+
nginx без proxy_protocol

или:

Xver 1
+
nginx с proxy_protocol

Нерабочая комбинация:

Xver 1
+
nginx без proxy_protocol

Также нерабочая:

Xver 0
+
nginx ожидает proxy_protocol

Если стороны не согласованы, обычный HTTPS fallback перестаёт нормально работать.


10. Проверка внутреннего backend после xver=1

После включения proxy_protocol обычный прямой:

curl https://127.0.0.1:8443

не является корректным тестом, потому что nginx ожидает PROXY header перед TLS handshake.

Использовать:

curl \
  --haproxy-protocol \
  --http2 \
  --resolve example.com:8443:127.0.0.1 \
  -I \
  https://example.com:8443/

Ожидается:

HTTP/2 200

Health check:

curl \
  --haproxy-protocol \
  --http2 \
  --resolve example.com:8443:127.0.0.1 \
  https://example.com:8443/healthz

Ожидается:

ok

Если локальный curl собран без HTTP/2, убрать --http2 и проверить ALPN через внешний 443.


11. Проверка HTTP/2 через внешний 443

Проверка:

curl --http2 -I https://example.com

Ожидается:

HTTP/2 200

ALPN:

openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  -alpn 'h2,http/1.1' \
  </dev/null 2>/dev/null \
  | grep -i 'ALPN protocol'

Ожидается:

ALPN protocol: h2

12. Проверка реального IP посетителя

Лог внутреннего nginx:

sudo tail -f /var/log/nginx/yazykow-reality-access.log

После запроса с другого устройства/сети первый IP в access log должен быть реальным IP посетителя.

Не должно быть:

127.0.0.1

Если там остаётся localhost, проверить:

REALITY Xver = 1

и:

listen 127.0.0.1:8443 ssl proxy_protocol;
real_ip_header proxy_protocol;
set_real_ip_from 127.0.0.1;

13. REALITY inbound

Публичный inbound:

Protocol:       VLESS
Port:           443
Security:       REALITY
Transport:      RAW / TCP
Flow:           xtls-rprx-vision

Target:         127.0.0.1:8443
Xver:           1

Server Names:
    example.com
    www.example.com

Клиентская сторона концептуально:

Address:       домен сервера
Port:          443
Protocol:      VLESS
Security:      REALITY
Flow:          xtls-rprx-vision
SNI:           example.com
Fingerprint:   chrome

Также требуются индивидуальные:

UUID
REALITY Public Key
Short ID

Они не включены в этот README.

Private Key REALITY также не включён.


14. Как работает Self-SNI

Обычный REALITY client:

client
  |
  | правильный UUID / public key / short id / SNI
  v
Xray :443
  |
  v
VLESS tunnel

Обычный браузер/сканер:

browser
   |
   | TLS ClientHello
   v
Xray :443
   |
   | не REALITY client
   |
   | PROXY protocol v1
   v
127.0.0.1:8443
   |
   v
nginx TLS + HTTP/2
   |
   v
настоящий сайт

Таким образом внешний 443 выглядит как настоящий HTTPS endpoint сайта.


15. 3x-ui panel

Systemd service:

sudo systemctl status x-ui

CLI:

sudo x-ui

Настройки:

sudo x-ui settings

База:

/etc/x-ui/x-ui.db

Panel bind:

127.0.0.1:25572

Проверка:

sudo ss -lntp | grep ':25572'

Правильно:

127.0.0.1:25572

Неправильно:

0.0.0.0:25572

или:

*:25572

16. Доступ к панели только через SSH tunnel

Порт панели наружу не открывается.

На клиентском компьютере создаётся SSH forwarding:

ssh -N \
  -L 25572:127.0.0.1:25572 \
  dds@SERVER_IP

Если используется отдельный SSH key:

ssh \
  -i ~/.ssh/dds_new_server \
  -N \
  -L 25572:127.0.0.1:25572 \
  dds@SERVER_IP

После создания tunnel браузер подключается к локальному 127.0.0.1:25572 с приватным Web Base Path.

Сам Web Base Path в этот README не записывается.


17. Subscription Server 3x-ui

Subscription Server используется отдельно от web-панели.

Параметры концептуально:

Enable Subscription: ON
Listen IP:           127.0.0.1
Port:                2096
Domain:              api.example.com
URI Path:            /offices-shown-liver-chapter/
Certificate File:    пусто
Private Key File:    пусто

TLS на 2096 не нужен: HTTPS завершается на внутреннем nginx.

Проверка listener:

sudo ss -lntp | grep ':2096'

Ожидается:

127.0.0.1:2096

Прямой локальный тест backend:

curl -i \
  -H 'Host: api.example.com' \
  "http://127.0.0.1:2096/offices-shown-liver-chapter/<SUB_ID>"

<SUB_ID> в README не хранится.

Внешняя цепочка:

client
  |
  | HTTPS :443
  v
api.example.com
  |
  v
Xray REALITY
  |
  | обычный TLS fallback
  | PROXY protocol v1
  v
nginx 127.0.0.1:8443
  |
  | /offices-shown-liver-chapter/
  v
127.0.0.1:2096
  |
  v
3x-ui Subscription Server

17.1. nginx для api.example.com

Отдельный HTTP server block нужен для ACME/redirect:

server {
    listen 80;
    listen [::]:80;

    server_name api.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type text/plain;
    }

    location / {
        return 301 https://$host$request_uri;
    }

    access_log /var/log/nginx/api-yazykow-access.log;
    error_log  /var/log/nginx/api-yazykow-error.log;
}

Внутренний TLS server block на 8443:

server {
    listen 127.0.0.1:8443 ssl proxy_protocol;
    http2 on;

    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

    real_ip_header proxy_protocol;
    set_real_ip_from 127.0.0.1;

    location ^~ /offices-shown-liver-chapter/ {
        proxy_pass http://127.0.0.1:2096;

        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port 443;

        proxy_set_header Range $http_range;
        proxy_set_header If-Range $http_if_range;

        proxy_redirect off;
    }

    location / {
        return 404;
    }

    access_log /var/log/nginx/api-yazykow-reality-access.log;
    error_log  /var/log/nginx/api-yazykow-reality-error.log;
}

Проверка через внутренний nginx:

curl \
  --haproxy-protocol \
  --http2 \
  --resolve api.example.com:8443:127.0.0.1 \
  -i \
  "https://api.example.com:8443/offices-shown-liver-chapter/<SUB_ID>"

Внешний тест:

curl -i \
  "https://api.example.com/offices-shown-liver-chapter/<SUB_ID>"

17.2. Отдельный VLESS XHTTP + REALITY

Основной REALITY на 443 не изменяется.

Добавлен отдельный inbound:

Port:          8444
Protocol:      VLESS
Transport:     XHTTP
Mode:          packet-up
Security:      REALITY
Flow:          пусто

Target:        127.0.0.1:8443
Xver:          1

Server Names:
    example.com
    www.example.com

Для этого inbound используются отдельные:

UUID
REALITY Private/Public Key
Short ID
XHTTP Path

Эти значения в README не записываются.

XHTTP transport:

Mode:          packet-up
Path:          /<RANDOM_XHTTP_PATH>
Host:          example.com
XMUX:          OFF
Flow:          пусто

Продвинутые XHTTP/session параметры без необходимости не меняются.

Логическая схема:

Internet :8444
      |
      v
Xray
VLESS + XHTTP
mode=packet-up
REALITY
xver=1
   /       \
  /         \
client       обычный TLS
 |               |
 v               |
VLESS tunnel     |
                 v
          PROXY protocol v1
                 |
                 v
          127.0.0.1:8443
                 |
                 v
               nginx
                 |
                 v
          настоящий сайт

Поскольку внутренний nginx ожидает PROXY protocol, XHTTP inbound также использует:

Xver = 1

Проверка listener:

sudo ss -lntp | grep ':8444'

Проверка обоих Xray ports:

sudo ss -lntp | grep -E ':(443|8444)\b'

Ожидается концептуально:

*:443     Xray
*:8444    Xray

Fallback-тест XHTTP inbound:

curl \
  --resolve example.com:8444:127.0.0.1 \
  --http2 \
  -I \
  https://example.com:8444/

Это проверяет только REALITY fallback и Self-SNI.

Сам XHTTP transport проверяется реальным Xray-compatible клиентом.

Клиент концептуально:

Protocol:      VLESS
Address:       основной домен
Port:          8444

UUID:          отдельный UUID
Flow:          пусто

Transport:     XHTTP
Mode:          packet-up
Path:          /<RANDOM_XHTTP_PATH>
Host:          example.com

Security:      REALITY
SNI:           example.com
Fingerprint:   chrome
Public Key:    отдельный Public Key
Short ID:      отдельный Short ID

18. Pinned Xray core

Установленная и зафиксированная версия:

Xray 26.7.28

Оригинальный binary:

/usr/local/x-ui/bin/xray-linux-amd64

Pinned-копия:

/opt/xray-pinned/

Проверка:

sudo /opt/xray-pinned/xray-linux-amd64 version

Ожидается версия:

Xray 26.7.28

19. XUI_BIN_FOLDER

3x-ui настроена использовать отдельный каталог Xray:

XUI_BIN_FOLDER=/opt/xray-pinned

Файл:

/etc/default/x-ui

Содержимое:

XUI_BIN_FOLDER=/opt/xray-pinned

Systemd unit содержит:

EnvironmentFile=-/etc/default/x-ui

Проверить файл:

sudo cat /etc/default/x-ui

Проверить environment реально запущенного процесса:

PID=$(systemctl show -p MainPID --value x-ui)

sudo cat /proc/$PID/environ \
  | tr '\0' '\n' \
  | grep '^XUI_BIN_FOLDER='

Ожидается:

XUI_BIN_FOLDER=/opt/xray-pinned

20. Важное ограничение pinned core

Pinned Xray защищает от неожиданной замены бинарника при обновлении панели.

Но 3x-ui всё ещё управляет процессом Xray.

То есть:

панель != Xray binary

но:

панель управляет Xray process/config

Некоторые операции панели могут перезапустить Xray.

Перед обновлением панели:

  1. сделать backup базы 3x-ui;
  2. сделать backup /opt/xray-pinned;
  3. зафиксировать текущую версию Xray;
  4. проверить требования новой версии 3x-ui;
  5. не использовать Update Xray, если core обновлять не планируется;
  6. после обновления проверить сайт и VLESS.

21. Backup 3x-ui

Каталог:

sudo mkdir -p /root/3x-ui-backups
sudo chmod 700 /root/3x-ui-backups

Установить sqlite3 при необходимости:

sudo apt install -y sqlite3

Backup SQLite базы:

sudo sqlite3 /etc/x-ui/x-ui.db \
  ".backup '/root/3x-ui-backups/x-ui-$(date +%Y%m%d-%H%M%S).db'"

Backup pinned Xray:

sudo tar \
  -C /opt \
  -czpf "/root/3x-ui-backups/xray-pinned-$(date +%Y%m%d-%H%M%S).tar.gz" \
  xray-pinned

Проверка:

sudo ls -lh /root/3x-ui-backups/

22. Nginx backups

Перед существенными изменениями конфигурации создавались копии /etc/nginx.

Пример паттернов:

/root/nginx-before-reality-*
/root/nginx-before-443-xray-*
/root/nginx-before-xver1-h2-*

Проверить:

sudo ls -ld /root/nginx-before-*

Перед любым восстановлением сначала проверить содержимое нужного backup.


23. Let's Encrypt

Сертификат внутреннего nginx:

/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem

Он используется на:

127.0.0.1:8443

Публичный 443 при этом слушает Xray.

Проверить сертификаты:

sudo certbot certificates

24. Certbot renewal через HTTP-01

Поскольку nginx больше не владеет публичным 443, renewal рекомендуется делать через HTTP-01/webroot на 80.

Webroot:

/var/www/letsencrypt

Концептуальная настройка:

sudo certbot reconfigure \
  --cert-name example.com \
  --authenticator webroot \
  --webroot-path /var/www/letsencrypt

Обязательная проверка:

sudo certbot renew --dry-run

После успешного renewal nginx должен перечитать сертификат.

Deploy hook:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy

Файл:

/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Содержимое:

#!/bin/sh
systemctl reload nginx

Создать:

sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF

sudo chmod 755 \
  /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

25. Firewall

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

SSH
80/tcp
443/tcp
8444/tcp

8444 нужен для отдельного XHTTP inbound:

sudo ufw allow 8444/tcp

Только localhost:

8443
18080
25572
2096

Проверка:

sudo ufw status numbered
sudo ss -lntup

Не добавлять public UFW rules для:

8443
18080
25572
2096

26. SSH security

Основной администратор:

dds

Административные действия:

sudo ...

Используется:

SSH key authentication

Отключено:

password authentication
root SSH login
keyboard-interactive authentication

Проверить:

sudo sshd -T | grep -Ei \
  'pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin'

Ожидается:

pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
permitrootlogin no

27. Полный health check

Сайт

curl -I https://example.com

HTTP/2

curl --http2 -I https://example.com

ALPN

openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  -alpn 'h2,http/1.1' \
  </dev/null 2>/dev/null \
  | grep -i 'ALPN protocol'

Ожидается:

ALPN protocol: h2

Django

curl http://127.0.0.1:18080/healthz

Ожидается:

ok

Internal TLS + PROXY protocol + HTTP/2

curl \
  --haproxy-protocol \
  --http2 \
  --resolve example.com:8443:127.0.0.1 \
  https://example.com:8443/healthz

Ожидается:

ok

Nginx

sudo nginx -t

Порты

sudo ss -lntp | grep -E ':(80|443|8444|8443|18080|25572|2096)\b'

Subscription backend

sudo ss -lntp | grep ':2096'

Subscription через nginx

curl \
  --haproxy-protocol \
  --http2 \
  --resolve api.example.com:8443:127.0.0.1 \
  -I \
  "https://api.example.com:8443/offices-shown-liver-chapter/<SUB_ID>"

XHTTP listener

sudo ss -lntp | grep ':8444'

XHTTP fallback

curl \
  --resolve example.com:8444:127.0.0.1 \
  --http2 \
  -I \
  https://example.com:8444/

X-ui

sudo systemctl status x-ui --no-pager

Xray

ps -ef | grep '[x]ray'

Pinned version

sudo /opt/xray-pinned/xray-linux-amd64 version

Docker

cd /opt/stacks/yazykow
docker compose ps

28. Проверка реального IP end-to-end

Открыть лог:

sudo tail -f /var/log/nginx/yazykow-reality-access.log

С другого устройства или другой сети сделать HTTPS-запрос к сайту.

В access log должен появиться внешний IP клиента.

Путь IP:

client public IP
       |
       v
Xray :443
       |
       | PROXY protocol v1
       v
nginx :8443
       |
       | real_ip_header proxy_protocol
       v
$remote_addr
       |
       +--> X-Real-IP
       |
       +--> X-Forwarded-For
       |
       v
Django

29. Диагностика: сайт не открывается

Проверить, кто занимает 443:

sudo ss -lntp | grep ':443'

Должен быть Xray.

Проверить nginx:

sudo nginx -t
sudo systemctl status nginx --no-pager

Проверить 8443:

sudo ss -lntp | grep ':8443'

Проверить internal backend с PROXY protocol:

curl \
  --haproxy-protocol \
  --resolve example.com:8443:127.0.0.1 \
  -I \
  https://example.com:8443/

Проверить Django:

curl http://127.0.0.1:18080/healthz

Проверить Xray/x-ui:

sudo systemctl status x-ui --no-pager
sudo journalctl -u x-ui -n 100 --no-pager

30. Диагностика: сайт сломался после смены Xver

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

REALITY:
Xver = 1

nginx:

listen 127.0.0.1:8443 ssl proxy_protocol;
real_ip_header proxy_protocol;
set_real_ip_from 127.0.0.1;

Если Xver и nginx proxy_protocol не согласованы, fallback работать не будет.


31. Диагностика: сайт работает, но IP всегда localhost

Проверить в 3x-ui:

Xver = 1

Проверить nginx:

real_ip_header proxy_protocol;
set_real_ip_from 127.0.0.1;

Проверить log:

sudo tail -f /var/log/nginx/yazykow-reality-access.log

32. Диагностика HTTP/2

Проверить nginx build:

sudo nginx -V 2>&1 | grep http_v2_module

Проверить конфиг:

http2 on;

Проверить внешний ALPN:

openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  -alpn 'h2,http/1.1' \
  </dev/null 2>/dev/null \
  | grep -i ALPN

33. Rollback REALITY -> обычный nginx

При серьёзной проблеме можно временно вернуть nginx непосредственно на публичный 443.

Общий порядок:

  1. освободить 443 от Xray;
  2. восстановить nginx-конфиг с публичным TLS listener;
  3. проверить nginx -t;
  4. reload nginx;
  5. проверить сайт.

Проверить backups:

sudo ls -dt /root/nginx-before-* 2>/dev/null

После восстановления:

sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com

Важно: restore выполнять только после проверки выбранной резервной копии.


34. Что нельзя делать вслепую

Не использовать без backup:

docker compose down -v

Не открывать 3x-ui panel наружу.

Не открывать наружу:

8443
18080
25572

Не удалять:

/etc/x-ui/x-ui.db
/opt/xray-pinned
/etc/letsencrypt
Docker volumes
PostgreSQL volumes

Не обновлять Xray core без backup.

Не менять только Xver, не меняя соответствующим образом nginx proxy_protocol.

Не менять только nginx proxy_protocol, оставляя несовместимый Xver.

Не запускать сторонние Self-SNI/fakesite install-скрипты поверх рабочей конфигурации.


35. Ключевые пути

# Site
/opt/stacks/yazykow/

# Public nginx HTTP
/etc/nginx/sites-available/example.com
/etc/nginx/sites-available/api.example.com

# REALITY target nginx
/etc/nginx/sites-available/yazykow-reality-backend
/etc/nginx/sites-enabled/yazykow-reality-backend

# Let's Encrypt
/etc/letsencrypt/live/example.com/
/etc/letsencrypt/live/api.example.com/
/var/www/letsencrypt/

# 3x-ui
/etc/x-ui/x-ui.db
/usr/local/x-ui/
/etc/default/x-ui

# Pinned Xray
/opt/xray-pinned/

# Backups
/root/3x-ui-backups/
/root/nginx-before-reality-*
/root/nginx-before-443-xray-*
/root/nginx-before-xver1-h2-*

36. Финальное состояние

Основной REALITY:

client
  |
  | TCP/443
  v
Xray
VLESS RAW/TCP + REALITY
xver=1
  |
  +--> REALITY client -> VLESS tunnel
  |
  +--> обычный TLS
          |
          | PROXY protocol v1
          v
      nginx 127.0.0.1:8443
          |
          v
      настоящий сайт

XHTTP:

client
  |
  | TCP/8444
  v
Xray
VLESS + XHTTP
mode=packet-up
REALITY
xver=1
  |
  +--> XHTTP/REALITY client -> VLESS tunnel
  |
  +--> обычный TLS
          |
          | PROXY protocol v1
          v
      nginx 127.0.0.1:8443
          |
          v
      настоящий сайт

Subscription:

api.example.com:443
        |
        v
Xray :443
        |
        | ordinary TLS fallback
        | PROXY protocol v1
        v
nginx 127.0.0.1:8443
        |
        | /offices-shown-liver-chapter/
        v
127.0.0.1:2096
        |
        v
3x-ui Subscription Server

Panel:

SSH tunnel
    |
    v
127.0.0.1:25572
    |
    v
3x-ui

Pinned Xray:

/opt/xray-pinned/xray-linux-amd64
Xray 26.7.28

Итоговые свойства:

80           -> nginx public HTTP / ACME
443          -> Xray / VLESS RAW + REALITY
8444         -> Xray / VLESS XHTTP packet-up + REALITY

8443         -> nginx localhost only
18080        -> Django localhost only
25572         -> 3x-ui panel localhost only
2096         -> Subscription Server localhost only

REALITY 443  -> xver=1
REALITY 8444 -> xver=1

nginx 8443   -> proxy_protocol
nginx 8443   -> HTTP/2
nginx        -> real client IP

api domain   -> subscription path -> 2096
Xray core    -> pinned отдельно от panel

Это текущая целевая конфигурация сервера.