Панель и нода на одном сервере
Панель и нода могут работать на одном сервере. Панели и Xray нужен один и тот же порт 443. Nginx принимает соединение, читает имя домена из TLS ClientHello (SNI) без расшифровки и по нему решает, куда отдать соединение:
клиент :443 ── Nginx stream (ssl_preread) ├─ SNI = panel.example.com → Nginx http 127.0.0.1:8443 → панель 127.0.0.1:3000 └─ любой другой SNI → Xray 127.0.0.1:9443 (REALITY)Чтобы панель и Xray видели настоящий IP клиента, а не 127.0.0.1, Nginx добавляет к соединению заголовок PROXY protocol, и обе стороны умеют его читать.
Во всех примерах замените panel.example.com на домен панели.
Что нужно
Заголовок раздела «Что нужно»-
Nginx с модулями
streamиssl_preread. Проверка:Окно терминала nginx -V 2>&1 | grep -o 'with-stream_ssl_preread_module'Debian / Ubuntu:
Окно терминала apt updateapt install nginx-fullRHEL-like:
Окно терминала dnf install nginx nginx-mod-streamУбедитесь, что динамический модуль
streamзагружается через штатные include модулей дистрибутива вnginx.conf. -
Панель на
127.0.0.1:3000, установленная через systemd или Docker. -
Порт 443 свободен; для HTTP-01 также нужен открытый и свободный порт 80.
1. Сертификат панели
Заголовок раздела «1. Сертификат панели»Выполняйте команды от root. Замените panel.example.com на домен панели. DNS-01 подтверждает владение доменом через TXT-запись и не требует входящих соединений на порты 80 и 443.
DNS-01 с автоматическим продлением
Заголовок раздела «DNS-01 с автоматическим продлением»Пример для домена с DNS в Cloudflare. Для другого провайдера установите его DNS-плагин Certbot или используйте ручной способ ниже.
Debian / Ubuntu:
apt updateapt install certbot python3-certbot-dns-cloudflareRHEL-like (предварительно подключите EPEL):
dnf install certbot python3-certbot-dns-cloudflareСоздайте API-токен с правом Zone:DNS:Edit, ограниченным зоной нужного домена. Сохраните его в файле, доступном только root, как описано в документации плагина:
install -d -m 700 /root/.secrets/certbotinstall -m 600 /dev/null /root/.secrets/certbot/cloudflare.ininano /root/.secrets/certbot/cloudflare.inidns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKENcertbot certonly --dns-cloudflare \ --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \ --dns-cloudflare-propagation-seconds 60 \ --cert-name panel.example.com -d panel.example.comРучной DNS-01
Заголовок раздела «Ручной DNS-01»Если у провайдера нет API-плагина, установите certbot через apt install certbot или dnf install certbot (с подключённым EPEL) и выполните:
certbot certonly --manual --preferred-challenges dns --cert-name panel.example.com -d panel.example.comCertbot покажет имя TXT-записи _acme-challenge.panel.example.com и её значение. Добавьте запись у DNS-провайдера, дождитесь её появления в публичном DNS и нажмите Enter в Certbot. После проверки запись можно удалить. Без authentication hooks при каждом продлении потребуется повторять ручную проверку.
Альтернатива: HTTP-01
Заголовок раздела «Альтернатива: HTTP-01»Обычный способ также доступен. Направьте A/AAAA-записи домена на этот сервер, откройте порт 80/TCP в файрволе и проверьте, что он свободен:
ss -ltnp 'sport = :80'certbot certonly --standalone --preferred-challenges http --cert-name panel.example.com -d panel.example.comОтключите стандартный сайт Nginx и другие конфиги, которые слушают порт 80. Certbot временно занимает этот порт для проверки. Конфиги ниже обслуживают только HTTPS.
Продление
Заголовок раздела «Продление»Файлы сертификата: /etc/letsencrypt/live/panel.example.com/fullchain.pem и privkey.pem. После продления перезагружайте Nginx через deploy hook:
install -d /etc/letsencrypt/renewal-hooks/deploycat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'EOF'#!/bin/shnginx -t && systemctl reload nginxEOFchmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shДля автоматического DNS-01 и HTTP-01 включите таймер установленного пакета (certbot.timer в Debian/Ubuntu, certbot-renew.timer в EPEL), затем проверьте продление после настройки Nginx:
systemctl enable --now certbot.timercertbot renew --dry-runВ RHEL-like используйте systemctl enable --now certbot-renew.timer. Плагин Cloudflare повторно использует сохранённый файл токена — оставьте его на сервере.
2. Разбор по SNI
Заголовок раздела «2. Разбор по SNI»Блок stream должен лежать на верхнем уровне nginx.conf, рядом с http, а не внутри него. Файлы из conf.d/ подключаются внутри http, поэтому туда этот блок класть нельзя. Создайте /etc/nginx/stream.conf:
stream { map $ssl_preread_server_name $sni_backend { panel.example.com 127.0.0.1:8443; default 127.0.0.1:9443; }
server { listen 443; listen [::]:443; ssl_preread on; proxy_pass $sni_backend; proxy_protocol on; }}И подключите его в конце /etc/nginx/nginx.conf, после закрывающей скобки блока http:
include /etc/nginx/stream.conf;Соединения без SNI (сканеры по IP) попадают в default, то есть в Xray, а REALITY отдаёт им сайт из target.
3. Панель за stream
Заголовок раздела «3. Панель за stream»Файл /etc/nginx/conf.d/gowave.conf. Сервер слушает только 127.0.0.1:8443 и ждёт PROXY protocol от блока stream:
map $http_upgrade $gowave_connection_upgrade { default upgrade; "" close;}
server { listen 127.0.0.1:8443 ssl proxy_protocol; http2 on; server_name panel.example.com;
# Настоящий IP клиента берётся из PROXY protocol set_real_ip_from 127.0.0.1; real_ip_header proxy_protocol;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; ssl_session_tickets off;
server_tokens off;
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
if ($host != "panel.example.com") { return 421; }
error_log /var/log/nginx/gowave.error.log;
client_max_body_size 50M;
gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_min_length 256; gzip_types application/javascript application/json application/wasm image/svg+xml text/css text/plain;
location / { proxy_http_version 1.1; proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $gowave_connection_upgrade; }}http2 on требует Nginx 1.25.1 и новее; на старых версиях замените его и строку listen на listen 127.0.0.1:8443 ssl http2 proxy_protocol;.
nginx -t && systemctl reload nginxlisten 443 ... default_server с ssl_reject_handshake из обычной настройки Nginx здесь не нужен: порт 443 занят блоком stream. nginx -t такую конфигурацию пропустит, но при запуске Nginx упадёт с bind() to 0.0.0.0:443 failed (98: Address in use).
4. Нода
Заголовок раздела «4. Нода»Порт 3000 уже занят панелью, поэтому Go-ноде нужен другой порт управления, например 2222. Установщик сначала скачивается целиком, его можно просмотреть перед запуском:
curl -fsSLo /tmp/remnanode-install.sh https://raw.githubusercontent.com/aaaSaZaN/remnanode-go/main/install.shsudo NODE_PORT=2222 sh /tmp/remnanode-install.shВ панели добавьте ноду с IP этого сервера и портом 2222.
5. Inbound в профиле конфигурации
Заголовок раздела «5. Inbound в профиле конфигурации»Xray слушает только 127.0.0.1:9443 и принимает PROXY protocol. Пример inbound для профиля конфигурации в панели:
{ "tag": "VLESS_REALITY", "listen": "127.0.0.1", "port": 9443, "protocol": "vless", "settings": { "clients": [], "decryption": "none" }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "target": "www.example.com:443", "serverNames": ["www.example.com"], "privateKey": "<приватный ключ из xray x25519>", "shortIds": [""] }, "sockopt": { "acceptProxyProtocol": true } }}www.example.com замените на сайт для REALITY. Он не должен совпадать с доменом панели, иначе Nginx отправит клиентов в панель.
В хосте для этого inbound укажите порт 443. Клиенты подключаются к Nginx, а 9443 - внутренний порт, снаружи он недоступен.
Частые ошибки
Заголовок раздела «Частые ошибки»- Блок
streamвнутриhttpили вconf.d/.nginx -tответит"stream" directive is not allowed here. - PROXY protocol включён не везде.
proxy_protocol onвstreamтребуетproxy_protocolвlistenу панели иacceptProxyProtocolу Xray. Если хотите без него, уберите его во всех трёх местах. Тогда панель будет видеть127.0.0.1вместо IP клиентов. - Панель на
listen 443. Сервер панели должен слушать127.0.0.1:8443. Порт 443 принадлежит только блокуstream. - В хосте порт
9443. Клиенты не смогут подключиться: снаружи открыт только 443. - Нода на порту
3000. Порт занят панелью, нода не запустится сAddress already in use. - Порт 80 занят.
certbot --standaloneответитCould not bind TCP port 80. Обычно его держит стандартный сайт Nginxdefaultили оставшийсяserverсlisten 80. Проверьтеss -ltnp 'sport = :80'. - Нет хука перезагрузки Nginx.
certbot renewпроходит успешно, но после истечения срока сертификата панель начинает отдавать просроченный сертификат.
Страница подписки на этом же сервере
Заголовок раздела «Страница подписки на этом же сервере»Выпустите для её домена сертификат той же командой из первого шага. Добавьте домен в map с тем же адресом 127.0.0.1:8443 и отдельный server с listen 127.0.0.1:8443 ssl proxy_protocol и своим server_name. Сама страница описана в разделе Страница подписки.