Ник Пост Дата
lazuli

Добрый день, настраиваю VLESS Reality с маскировкой под свой сайт и столкнулся с проблемой.

Настроил так, чтобы на порту 443 висел и nginx с сайтом, и xray XTLS-Reality. Если хотят зайти на один из моих сайтов - nginx обрабатывает всё как обычно, если заходят на домен vpn, то запрос перенаправляется в xray.

Но при url тесте в nekoray и попытках подключения к VPN соединение не удаётся. Логи nginx показывают только это:

[25/Dec/2024:18:41:17 +0000] "GET / HTTP/2.0" 200 746 "-" "Chrome"
[25/Dec/2024:18:41:17 +0000] "GET / HTTP/2.0" 200 746 "-" "Chrome"
[25/Dec/2024:18:41:17 +0000] "GET / HTTP/2.0" 200 746 "-" "Chrome"
[25/Dec/2024:18:41:18 +0000] "GET / HTTP/2.0" 200 746 "-" "Chrome"

В логах 3X-UI ничего нет.
При этом, если использовать маскировку под чужой домен, всё работает нормально.

Мой nginx.conf:

user nginx;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

stream {
    map $ssl_preread_server_name $name {
        site.mydomain.com             www;
        othersite.mydomain.com     www;
      	vpn.mydomain.com             xray;
        default                                 xray;
    }

    upstream xray {
        server 127.0.0.1:8050;
    }

    upstream www {
        server 127.0.0.1:7443;
    }

    server {
        listen               443;
        listen               [::]:443;
        proxy_pass      $name;
        ssl_preread     on;
    }

    server {
        listen 127.0.0.1:8050;
        proxy_pass 127.0.0.1:8443;
    }
}

http {
    include /etc/nginx/mime.types;
    server {
        listen 80;
        listen [::]:80;
        return 301 https://$host$request_uri;
    }

    server {	
        listen 127.0.0.1:7443 ssl;
    	http2 on;

        server_name site.mydomain.com;

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

    	ssl_protocols TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

        root /var/www/site.mydomain.com;
        index index.html;

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

Конфиг подключения, указан 8443 порт, но подключаюсь по 443:

У меня мало опыта работы с nginx, и VPN я поднимаю впервые, буду рад советам и любой помощи, спасибо!

2024-12-26T14:58:39.150Z
NowAndThen

Как есть вы должны подключаться к Xray по 8443, он там вас ждет. А на 443 вас встречает Nginx, понятно, что он знать не знает ни про какие VLESS ключи и туннели.

    server {
        listen               443;
        listen               [::]:443;
        proxy_pass      $name;
        ssl_preread     on;
    }

Если хотите по 443 ходить, то вот этот блок закомментите весь, а в Xray в Port пропишите 443. Правильная логика: фейс контролем на входе на 443 должен работать Xray, а если вы ключом не подошли, перекидивать вас на сайт на Nginx. Т.е. на 443 порту должен слушать только Xray.

2024-12-26T15:22:24.308Z
lazuli

Спасибо! Всё оказалось проще, чем я думал)

2024-12-26T15:49:39.160Z
lazuli

После маскировки под свой сайт пинг увеличился в 2 раза) когда маскировался под www.ign.com пинг был 67-70мс, теперь стал 120-130мс, с чем это может быть связано?
UPD: отбой, похоже проблема была никак не связана с маскировкой и с моей машиной вообще, просто был сбой на стороне хостера

2024-12-26T19:04:54.055Z
DeHb86(Danila)

Добрый день!
Можете подсказать по какому гайду все это делали?
Вожусь уже второй день, и никак не могу заставить работать такой вариант, чтобы маскироваться своим доменом.

2024-12-26T19:39:09.153Z
Nocturnal-ru(Roman)

никаких гайдов особо нет, вот код моего nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
}

http {

	sendfile on;
	tcp_nopush on;
	types_hash_max_size 2048;
	include /etc/nginx/mime.types;
	default_type application/octet-stream;
	access_log /var/log/nginx/access.log;
	gzip on;
	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}

вот содержимое nginx/sites-available/default.conf

server {
	listen 80 default_server;
	listen 1111 ssl default_server;
	listen 8443 ssl default_server;
        server_name yourdomain.name;
	
	root /var/www/html;
	# Разрешить доступ к index.html
    	location = /index.html {
        index index.html;
    }

    	# Разрешить доступ к favicon.ico
    	location = /favicon.ico {
        # Можно указать root или, если файл находится в том же каталоге:
        root /var/www/html;
    }

    	# Разрешить доступ к корню, который отдает index.html
    	location = / {
        try_files /index.html =403;
    }

    	# Все остальные запросы блокируются
    	location / {
        return 403;
    }
        ssl_certificate /root/cert/yourdomain.name/fullchain.pem;
        ssl_certificate_key /root/cert/yourdomain.name/privkey.pem;
	ssl_session_cache shared:le_nginx_SSL:10m;
	ssl_session_timeout 1440m;
	ssl_session_tickets off;
	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_prefer_server_ciphers off;
	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";
}

содержимое nginx/sites-available/static_site

server {

    server_name localhost;

    location / {
        root /var/www/html;
        index index.html;
    }
}

соответственно в панели 3x-ui
port: 443 (панель его слушает, в случае если нет успешной авторизации - кидает его в nginx на 1111 порт)
dest: 127.0.0.1:1111
SNI: yourdomain.name
yourdomain.name - меняешь на свой домен, пути к SSL сертификату тоже надо поставить актуальные
ессно по пути /var/www/html/ предполагается наличие некого index.html и favicon.ico
еще лишние порты закрыть, 1111 оставить только для localhost на всякий

2024-12-26T19:52:01.972Z
allula

Устанавливаешь nginx. Настраиваешь его слушать ssl соединение на 127.0.0.1:port, добавляешь пути к сертификатам (выше скинули примерный конфиг). Главное, в качестве порта не указывай 443, потому что на нём будет XRay сидеть. В настройках REALITY в качестве Dest указываешь 127.0.0.1port, а в SNI запихиваешь свой домен, если покупал.

2024-12-26T19:53:29.644Z
xX_RUP3R7_P4UL50N_Xx

Вот как вариант, довольно хороший пример конфигураций nginx / сервера / клиента - тык

2024-12-26T19:54:01.323Z
xX_RUP3R7_P4UL50N_Xx

Хм, интересно… То есть можно без своего домена сделать steal oneself? Если же нет, и домен в любом случае нужен, то подойдёт ли бесплатный домен третьего уровня по типу qwertyuiop.duckdns.org?

2024-12-26T19:56:43.043Z
Nocturnal-ru(Roman)

без своего домена не получится, но домен 3его уровня теоретически можно, LE вроде как даст SSL сертификат

2024-12-26T20:05:06.314Z
DeHb86(Danila)

Вот на него то я и наткнулся ранее (только на сначала на эту ссылку Xray REALITY with 'steal oneself') и целый день мучался… Даже сайт стал мимикрировать под livelove-anime.jp )) Но вот само подлюкчение к vps через клиента не заработало…
Возможно я уже на vps-ке накрутил за последние два дня экспериментов.
В общем сброшу ее и начну по новой.

2024-12-26T20:05:19.269Z
Nocturnal-ru(Roman)

выше я выложил 100% работоспособный конфиг и настройки со своей vps

2024-12-26T20:06:40.187Z
NowAndThen

Можете попробовать вот как тут описывал. Общо, но зато проще, чем у Чики-китайца настраивается.

2024-12-26T20:20:46.300Z
allula

Можно, но будут нужны самозаверенные сертификаты. Про такой вариант я писал здесь. Если надо, могу подробнее рассказать, как это делается, но у меня сейчас нет уверенности, что такая схема сможет пройти active probing, ради которого REALITY и создавался. Если вас такое не смущает, то в SNI можно указывать что угодно или не указывать вообще ничего (даже неважно, под свой сайт вы маскируетесь или нет), но отчасти теряются преимущества REALITY, как я сказал.

2024-12-26T20:23:48.636Z
lazuli

К сожалению, никаких гайдов именно по маскировке под собственный домен не находил, приходилось по крупицам искать инфу. Но могу поделиться всеми конфигами.

Xray в итоге должен работать на 443 порту, в dest нужно указать локалхост - 127.0.0.1 и какой-нибудь другой порт, например 7443. в SNI указываете домен своего сайта, под который маскируетесь.

И nginx должен слушать этот 127.0.0.1:7443 для вашего маскировочного сайта.
Мой итоговый nginx.conf:

user nginx;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    server {
        listen 80;
        listen [::]:80;
        return 301 https://$host$request_uri;
    }

    server {	
        listen 127.0.0.1:7443 ssl;
	    http2 on;

        server_name site.mydomain.com;

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

	    ssl_protocols TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

        root /var/www/site.mydomain.com;
        index index.html;

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

site.mydomain.com - ваш домен, под который вы притворяетесь. И домен должен быть привязан именно к этой машине через указание ip адреса машины в A-записи.

Если nginx у вас работает в докере, как и у меня, то нужно указать network_mode: host для nginx в docker-compose.yml или пробросить необходимые порты

А так не совсем понятно, с чем у вас возникли проблемы, поэтому полностью всё расписать не могу

2024-12-26T20:26:12.726Z
throwaway1

Тоже недавно настраивал VLESS-REALITY-Steal-Oneself, на примере вот отсюда:

получилось что-то вроде такого:

nginx.conf
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use                epoll;
    multi_accept       on;
}

http {
    log_format main '[$time_local] $proxy_protocol_addr "$http_referer" "$http_user_agent"';
    access_log /var/log/nginx/access.log main;

    server_tokens   off;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ""      close;
    }

    map $proxy_protocol_addr $proxy_forwarded_elem {
        ~^[0-9.]+$        "for=$proxy_protocol_addr";
        ~^[0-9A-Fa-f:.]+$ "for=\"[$proxy_protocol_addr]\"";
        default           "for=unknown";
    }

    map $http_forwarded $proxy_add_forwarded {
        "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";
        default "$proxy_forwarded_elem";
    }

    server {
        listen 80;
        listen [::]:80;
        return 301 https://$host$request_uri;
    }

    server {
        listen                  127.0.0.1:8001 ssl default_server;

        ssl_reject_handshake    on;

        ssl_protocols           TLSv1.3;

        ssl_session_timeout     1h;
        ssl_session_cache       shared:SSL:10m;
    }

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

        set_real_ip_from           127.0.0.1;
        real_ip_header             proxy_protocol;

        server_name                subdomain.domain.com; # Твой домен

        ssl_certificate            /root/cert/subdomain.domain.com/fullchain.pem; # Сертификат
        ssl_certificate_key        /root/cert/subdomain.domain.com/privkey.pem; # Приватный ключ

        ssl_protocols              TLSv1.3;
        ssl_ciphers                TLS13_AES_128_GCM_SHA256:TLS13_AES_256_GCM_SHA384:TLS13_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305;
        ssl_prefer_server_ciphers  on;

        ssl_stapling               on;
        ssl_stapling_verify        on;
        resolver                   1.1.1.1 valid=60s;
        resolver_timeout           2s;

        location / {
            sub_filter                            $proxy_host $host;
            sub_filter_once                       off;

	    #deny                                  all; # Если хочешь иметь 403 Forbidden при заходе на сайт, откоменчиваешь эту строку и закоменчиваешь две снизу
	    set $website                          www.lovelive-anime.jp; # Сюда можно добавить любую ссылку, контент с этого сайта будет отображаться при заходе на твой сайт
	    proxy_pass                            https://$website;
            resolver                              1.1.1.1;

            proxy_set_header Host                 $proxy_host;

            proxy_http_version                    1.1;
            proxy_cache_bypass                    $http_upgrade;

            proxy_ssl_server_name                 on;

            proxy_set_header Upgrade              $http_upgrade;
            proxy_set_header Connection           $connection_upgrade;
            proxy_set_header X-Real-IP            $proxy_protocol_addr;
            proxy_set_header Forwarded            $proxy_add_forwarded;
            proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto    $scheme;
            proxy_set_header X-Forwarded-Host     $host;
            proxy_set_header X-Forwarded-Port     $server_port;

            proxy_connect_timeout                 60s;
            proxy_send_timeout                    60s;
            proxy_read_timeout                    60s;
        }
    }
}
Настройки 3x-ui


2024-12-26T21:00:06.963Z
naykaminka(Sergey)

В чем смысл маскироваться под свой сайт ? Делать вид что там файлопомойка легальная ?

2024-12-26T22:32:49.589Z
throwaway1

Я далеко не эксперт, но вот мои мысли по поводу этого:

  1. Потенциально меньше задержек и более стабильное соединение, благодаря тому, что сервер и твой домен для маскировки висят на одном айпи
  2. Когда маскируешься под чужой домен, ты отправляешь на него довольно много запросов и владелец сайта может заблеклистить айпи твоего сервера, соответственно v+r сразу же отвалится
  3. Как ты и сказал, на своем домене можно поднять какой-нибудь некстклауд и притвориться файлопомойкой, чтобы у цензора не было вопросов к количеству трафика, который ты гоняешь через свой домен
  4. При наличии своего домена, можно настроить проксирование через CDN, благодаря новомодному XHTTP или “устаревшему” gRPC, что сделает твой прокси еще более устойчивым к потенциальным нападкам от цензора
2024-12-27T01:36:59.401Z
NowAndThen

А с чего от вас запросы идут на воруемый SNI? Когда вы заходите на Xray вы устанавливаете туннель и работаете через него, на условный yahoo.com:443 вы вообще не обращаетесь. Туда Xray перекидывает не ваши VLESS, а прочие запросы. Это только одинокий РКН, который когда-то, возможно, придет вас пощупать. И случайные школоботы, которые пришли вас сканировать на предмет дыр. Так они все подряд сканируют. А на yahoo перекинутся только те, которые к вам почему-то по 443 обращаются. А кому еще нужен ваш IP? Там серьезному трафику на воруемый домен неоткуда взяться.

2024-12-27T02:55:26.336Z
0ka(0ka)

Reality на каждый запрос (каждый открытый сайт с клиента это запрос) делает обращение к заданному sni чтобы получить его сертификат и передать клиенту

2024-12-27T04:38:44.533Z
Nocturnal-ru(Roman)

помоему всеже только те запросы, которые “не прошли авторизацию”, те условный цензор или некие мимокрокодилы

2024-12-27T22:07:40.129Z
0ka(0ka)

не предполагайте а протестируйте сами

2024-12-28T01:47:05.472Z
NowAndThen

А каким инструментарием это можно посмотреть. Wireshark?

2024-12-28T02:07:14.767Z
0ka(0ka)

возьмите yahoo.com, в /etc/hosts сервера добавьте только один его айпи адрес, напр.
98.137.11.163 yahoo.com
поставьте его в реалити конфиг и на сервере запустите tcpdump -n -f "host 98.137.11.163 and outbound and tcp[tcpflags] & (tcp-syn) != 0
затем запустите конфиг на клиенте и начинайте серфить инет, увидите в логе tcpdump что каждый открытый сайт добавляет строчку в лог (запрос на yahoo)

2024-12-28T02:26:56.550Z
DeHb86(Danila)

Никогда не поздно сказать спасибо, всем кто откликнулся. Не прошло и несколько дней, снова удалось сесть за компьютер. И все удалось!
Еще раз большое спасибо. Пошел сохранять себе этот мануал :slight_smile:

2024-12-29T18:01:48.002Z
wsvall

Настроил маскировку под свой сайт (вернее под заглушку логин\пароль) читая посты тут.
Знатоки, посмотрите, пожалуйста, конфиги может что-то улучшить можно? С такой настройкой можно спокойно жить?

Nginx
server {

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name mydomain.com www.mydomain.com;

        location / {
                auth_basic "Video Area";
                auth_basic_user_file /etc/htpasswd;
        }


    #listen 443 ssl; # managed by Certbot
    listen 127.0.0.1:1234 ssl http2;
    listen [::1]:1234 ssl http2;
    ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mydomain.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 = mydomain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

        server_name mydomain.com;
    listen 80;
    return 404; # managed by Certbot

}
3xui

2025-01-06T12:34:33.725Z
xX_RUP3R7_P4UL50N_Xx

Вполне, у меня почти так же (разве что REALITY вместо XHTTP и включён sniffing http+tls+quic+fakedns, без двух галочек only).

Я бы на вашем месте включил Sniffing и настроил роутинг российских айпишников / сайтов в WARP, так - на всякий случай.

2025-01-06T12:58:22.684Z
wsvall

А что даст включение Sniffing, я смогу видеть куда ходят клиенты (т.е. я) ?

И зачем ру сайты в WARP ? Я на них хожу без проксика (на стороне клиента маршруты заданы)

2025-01-06T13:07:06.522Z
0ka(0ka)

даёт возможность делать роутинг по доменам

2025-01-06T13:10:13.428Z
xX_RUP3R7_P4UL50N_Xx

Как уже ответили выше - даёт роутить по доменам. Зачем? Для лучшей маскировки от РКН и минимизации риска блокировки по подозрению в использовании сервера как прокси, а не в качестве обычного сайта.

Даже если настроен роутинг напрямую в клиенте (у меня, кстати, тоже), кмк лишней данная настройка не будет, это своего рода второй слой защиты от утечек RU трафика.

Да, в России пока не было задокументированно блокировок по этому паттерну, как это уже давно делается в Китае, но нет никаких гарантий что это не будут делать завтра + в данный момент не собираются айпишники в серую базу данных.

2025-01-06T13:18:25.202Z
wsvall

Спасибо, за ответы. Sniffing включил, а дальше с WARP я кажется завис), не могли бы поподробней рассказать об добавлении WARP в эту схему, это делается на стороне сервера? какие маршруты указать ? Т.е. с использованием WARP я скрою подключение к VPS?
На клиенте у меня используются фильтры “все что заблокировано в Россиии пускать в прокси”.

нешел настройку WARP в панели это оно?

Вы это имелии ввиду:

?

2025-01-06T14:09:26.975Z
xX_RUP3R7_P4UL50N_Xx

Да, примерно так. Всё просто:

  1. Создаёте outbound WARP (Generate, затем Add)

  2. Создаёте правила, в outbound выбираете WARP

  3. Сохраняете настройки и перезапускаете Xray

Я у себя так настроил, в принципе этого достаточно (дополнительно прописал deepmind, это нужно для работы AI сервисов от гугла, т.к. с ним могут возникать проблемы если гугл занесёт ваш IP в категорию “из России”, а ntc добавил чисто для большей приватности и в качестве примера):

  • Сначала правила для доменов geosite:category-ru,domain:livejournal.com,domain:icq.com,domain:by,geosite:google-deepmind,domain:ntc.party

  • Потом правила для IP geoip:ru,geoip:by

2025-01-06T15:05:30.425Z
wsvall

Как интересно, спасибо!
Итого, созданный на сервере Outbound в WARP (с маршрутом, например, geoip:ru)

  • позволит дать клиенту ответ на его запрос к сайт.ру не с самого VPS, а с IP из сети WARP
  • будет работать только, если у клиента отсуствует правило маршрутизации (geoip:ru → direct)
  • tcpdump -i ens3 -nn -s 0 -v host 162.159.192.1 на сервере покажет трафик только, когда на клиенте выключен маршрут geoip:ru → direct
  • при посещении сайта ipmy.ru, который маршрутизируется с клиента в прокси (чисто для эксперимента) будет виден IP из WARP, а не IP VPS или домашний IP

У меня все 4е пункта с ответом да, может ли это означать, что добавленый в схему WARP работает как надо?

2025-01-06T16:16:48.464Z
xX_RUP3R7_P4UL50N_Xx

Да, всё правильно

2025-01-06T16:23:36.342Z
soulvvv

Добрый день, настраиваю nginx для балансировки по SNI на vps несколько сайтов, один заглушка для vless+reality. желаемое поведение что бы при обращении на sni xray выдавало сайт заглушку. ну и остальные сайты работали.
сейчас остальные сайты работают но xray не работает и заглушка отдаёт SSL_ERROR_RX_RECORD_TOO_LONG
подскажите пожалуйста куда смотреть, что я делаю не так?
или не изгаляться с reality и сделать vless+TLS?

текущий конфиг nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
load_module
events {
    worker_connections 768;
    # multi_accept on;
}

stream {
    map $ssl_preread_server_name $name {
        blog.example.com backend_blog;
        dev.example.com backend_dev;
        default backend_discard;
    }
    #just www
    upstream backend_blog {
        server 127.0.0.1:8001;
    }
    #xray reality vless
    upstream backend_dev {
        server 127.0.0.1:8003;
    }
    #reject wrong sni
    upstream backend_discard {
        server 127.0.0.1:8011;
    }

    server {
        listen 443;
        proxy_pass $name;
        ssl_preread on;

        proxy_protocol on;
    }
}


http {
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    log_format main '[$time_local] $proxy_protocol_addr "$http_referer" "$http_user_agent"';
    access_log /var/log/nginx/access.log main;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        "" close;
    }

    map $proxy_protocol_addr $proxy_forwarded_elem {
        ~^[0-9.]+$ "for=$proxy_protocol_addr";
        ~^[0-9A-Fa-f:.]+$ "for=\"[$proxy_protocol_addr]\"";
        default "for=unknown";
    }

    map $http_forwarded $proxy_add_forwarded {
        "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";
        default "$proxy_forwarded_elem";
    }
    #redirect on ssl
    server {
        if ($host = blog.example.com) {
            return 301 https://$host$request_uri;
        }
        if ($host = dev.example.com) {
            return 301 https://$host$request_uri;
        }

        listen 80 default_server;
        return 404;
    }
    #reject wrong sni
    server {
        listen 127.0.0.1:8011 ssl proxy_protocol;

        ssl_reject_handshake on;

        ssl_protocols TLSv1.2 TLSv1.3;
    }


    include /etc/letsencrypt/options-ssl-nginx.conf;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
конфиг dev.example.com
server {
    listen 127.0.0.1:8004 ssl proxy_protocol;

    ssl_certificate /etc/letsencrypt/live/dev.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dev.example.com/privkey.pem;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.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>

    root /var/www/html/dev.example.com;
    index index.html;

    client_header_timeout 5m;
    keepalive_timeout 5m;

    location / {
        try_files $uri $uri/ =404;
    }
}
конфиг xray
{
    "log": {
        "loglevel": "warning",
        "access": "/var/log/xray/access.log",
        "error": "/var/log/xray/error.log"
    },
    "dns": {
        "servers": [
            "https+local://1.1.1.1/dns-query",
            "localhost"
        ]
    },
    "routing": {
        "domainStrategy": "IPIfNonMatch",
        "rules": [
            {
                "type": "field",
                "ip": [
                    "geoip:private"
                ],
                "outboundTag": "discard"
            },
            {
                "type": "field",
                "domain": [
                    "geosite:category-ads-all"
                ],
                "outboundTag": "discard"
            }
        ]
    },
    "inbounds": [
        {
            "listen": "127.0.0.1",
            "port": 8003,
            "protocol": "vless",
            "tag": "in-vless-1",
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
            },
            "settings": {
                "clients": [
                    {
                        "id": "1be50faf-751e-4e7e-ba15-5c3afbcaa2de",
                        "level": 0,
                        "email": "love@localhost.local"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "raw",
                "security": "reality",
                "realitySettings": {
                    "target": "127.0.0.1:8004",
                    "xver": 1,
                    "serverNames": [
                        "dev.example.com"
                    ],
                    "privateKey": "тут есть серт",
                    "shortIds": [
                        "тут они есть и корректно используются"
                    ]
                }
            },
            "tcpSettings": {
                "acceptProxyProtocol": true
            }
        }
    ],
    "outbounds": [
        {
            "tag": "russia",
            "protocol": "freedom"
        },
        {
            "tag": "discard",
            "protocol": "blackhole"
        }
    ]
}
2025-01-12T14:28:54.577Z
sakontwist

Если вы используете свой сертификат, то почему security: reality, а не tls?

В такой схеме можно использовать обратное решение - повесить на внешний порт 443 docodemo-door (inbound) и включить на нем снифер. В зависимости от domain сделать rules, которые будут раскидывать куда вам нужно - на нормальный xray (127.0.0.1:8003) или nginx (можно тоже на 127.0.0.1).

2025-01-14T09:07:11.922Z
sakontwist

Вот тут Заметки: Передача имени из SNI на upstream в Nginx - рекомендуют использовать в upstream proxy_ssl_server_name on;

2025-01-14T09:20:08.385Z
soulvvv

Да, уже переделал на TLS, хочется всё же решить задачку силами nginx.
Видимо что-то я недопонимаю в работе revers-proxy и секции stream.
Спасибо за наводки изучу и потестирую ещё. если добьюсь рабочего конфига постараюсь не забыть и выложить сюда.

2025-01-14T21:04:35.057Z
xxphantom(Dmitriy)

Если хочется держать на одном VPS и свой сайт(сайты) и XRAY, можно таки сделать, чтобы nginx смотрел первым, и перенаправлял трафик в xray (и он при этом может маскироваться под ваш же сайт, не проблема).

Рабочий мануал, по нему делал и всё ок: Xray с XTLS-Reality и Nginx на одном порту | Мини-блог

Только внимательней, там автор случайно в конфиге вставил после номера порта ссылку на https://github.com/XTLS/Xray-core/releases/, смотрите что копируете)

Если же хотите, чтобы в инет смотрел XRAY, то можно, как выше писали, для XRay оставить 443 порт, а на NGINX поднять сайт на 127.0.0.1:8443 и под него маскироваться уже в Xray

2025-01-16T11:46:53.017Z
soulvvv

Как обещал делюсь рабочим конфигом для NGINX(reverse-proxy+web+sub)+XRAY(VLESS-TLS)
Я точно не эксперт в nginx но конфиг рабочий, своё дело делает. думаю кому-то может пригодится. Буду рад замечаниям и предложениям по улучшению.

nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
load_module
events {
    worker_connections 768;
    # multi_accept on;
}

stream {
    map $ssl_preread_server_name $name {
        blog.example.com backend_blog;
        dev.example.com backend_dev;
        default backend_discard;
    }
    #just www
    upstream backend_blog {
        server 127.0.0.1:8001;
    }
    #xray reality vless
    upstream backend_dev {
        server 127.0.0.1:8003;

    }
    }
    #reject wrong sni
    upstream backend_discard {
        server 127.0.0.1:8011;
    }

    server {
        listen 443;
        listen [::]:443;
        proxy_pass $name;
        ssl_preread on;
        proxy_protocol on;
    }
}


http {
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    log_format main '[$time_local] $proxy_protocol_addr "$http_referer" "$http_user_agent"';
    access_log /var/log/nginx/access.log main;

    #redirect on ssl
    server {
        if ($host = blog.example.com) {
            return 301 https://$host$request_uri;
        }
        if ($host = dev.example.com) {
            return 301 https://$host$request_uri;
        }
        if ($host = ipam.example.com) {
            return 301 https://$host$request_uri;
        }

        listen 80 default_server;
        listen [::]:80 default_server;
        return 404;
    }
    #reject wrong sni
    server {
        listen 127.0.0.1:8011 ssl proxy_protocol;

        ssl_reject_handshake on;

        ssl_protocols TLSv1.2 TLSv1.3;
    }


    include /etc/letsencrypt/options-ssl-nginx.conf;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
dev.conf
server {
    listen 127.0.0.1:8004 proxy_protocol;
    listen 127.0.0.1:8005 http2 proxy_protocol;
    server_name dev.example.com;

    access_log /var/log/nginx/dev.example.com.access.log;
    error_log /var/log/nginx/dev.example.com.error.log;

    root /var/www/html/dev.example.com;
    index index.html;

    client_header_timeout 5m;
    keepalive_timeout 5m;

    location / {
        try_files $uri $uri/ =404;
    }
    location /random_string/ {
        try_files $uri /random_string/$uri
        add_header Cache-Control 'must-revalidate';
        add_header Content-Type text/plain;
    }
}
xray.json
{
    "log": {
        "loglevel": "warning",
        "access": "/var/log/xray/access.log",
        "error": "/var/log/xray/error.log"
    },
    "dns": {
        "servers": [
            "https+local://1.1.1.1/dns-query",
            "localhost"
        ]
    },
    "inbounds": [
        {
            "listen": "127.0.0.1",
            "port": 8003,
            "protocol": "vless",
            "tag": "in-vless-1",
            "sniffing": {
                "enabled": true,
                "routeOnly": true,
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
            },
            "settings": {
                "clients": [
                    {
                        "id": "user",
                        "flow": "xtls-rprx-vision",
                        "level": 0
                    }
                ],
                "decryption": "none",
                "fallbacks": [
                    {
                        "dest": 8004,
                        "xver": 1
                    },
                    {
                        "alpn": "h2",
                        "dest": 8005,
                        "xver": 1
                    }
                ]
            },
            "streamSettings": {
                "network": "raw",
                "rawSettings": {
                    "acceptProxyProtocol": true
                },
                "security": "tls",
                "tlsSettings": {
                    "serverNames": "dev.example.com",
                    "rejectUnknownSni": true,
                    "allowInsecure": false,
                    "alpn": [
                        "h2",
                        "http/1.1"
                    ],
                    "minVersion": "1.2",
                    "maxVersion": "1.3",
                    "certificates": [
                        {
                            "certificateFile": "/etc/letsencrypt/live/dev.example.com/fullchain.pem",
                            "keyFile": "/etc/letsencrypt/live/dev.example.com/privkey.pem"
                        }
                    ]
                }
            }
        }
    ],
    "outbounds": [
        {
            "tag": "vless-reality-1",
            "protocol": "vless",
            "settings": {
                "vnext": [
                    {
                        "address": "dev2.example.com",
                        "port": 443,
                        "users": [
                            {
                                "id": "dev1",
                                "flow": "xtls-rprx-vision",
                                "encryption": "none"
                            }
                        ]
                    }
                ]
            },
            "streamSettings": {
                "network": "raw",
                "rawSettings": {},
                "security": "reality",
                "realitySettings": {
                    "fingerprint": "firefox",
                    "publicKey": "key",
                    "serverName": "dev2.example.com",
                    "shortId": "id"
                }
            }
        },
        {
            "tag": "russia",
            "protocol": "freedom"
        },
        {
            "tag": "discard",
            "protocol": "blackhole"
        }
    ],
    "routing": {
        "domainStrategy": "IPIfNonMatch",
        "rules": [
            {
                "ip": [
                    "geoip:private"
                ],
                "outboundTag": "discard"
            },
            {
                "domain": [
                    "geosite:category-ads-all"
                ],
                "outboundTag": "discard"
            },
            {
                "protocol": [
                    "bittorrent"
                ],
                "outboundTag": "discard"
            },
            {
                "ip": [
                    "geoip:ru"
                ],
                "outboundTag": "russia"
            },
            {
                "domain": [
                    ".ru",
                    "geosite:apple",
                    "youtube.com",
                    "youtu.be",
                    "yt.be",
                    "googlevideo.com",
                    "ytimg.com",
                    "ggpht.com",
                    "gvt1.com",
                    "youtube-nocookie.com",
                    "youtube-ui.l.google.com",
                    "youtubeembeddedplayer.googleapis.com",
                    "youtube.googleapis.com",
                    "youtubei.googleapis.com",
                    "yt-video-upload.l.google.com",
                    "wide-youtube.l.google.com"
                ],
                "outboundTag": "russia"
            }

        ]
    }
}
2025-03-15T13:24:34.118Z