Помощь с настройкой проксирования через CDN (Websocket / gRPC)

Просто в секцию стрима не прописать, что-то хочет от меня:
nginx: [emerg] duplicate listen options for 0.0.0.0:443 in /etc/nginx/nginx.conf:57
nginx: configuration file /etc/nginx/nginx.conf test failed

Наверное мне стоило расписать подробнее.
Добавляешь в секцию стрима

log_format custom_stream_log 'proxy: $status $protocol remote_addr $remote_addr, sent_to $upstream_addr';

А ниже есть подсекция server, где происходит проксирование на бэкенды, туда нужно добавить строку

access_log      syslog:server=unix:/dev/log custom_stream_log;

дабы логи писались.
т.е. не добавляем еще один server, а добавляем логирование в имеющийся.

Сделал, ругается на логи:
nginx: [emerg] unknown log format “custom_stream_log” in /etc/nginx/nginx.conf:53
nginx: configuration file /etc/nginx/nginx.conf test failed

А можно на nginx.conf взглянуть?

Естественно:

user  nginx;
worker_processes  auto;

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


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

stream {
  map $ssl_preread_server_name $backend {
      XXXXX.com               reality;
      XXXXX.XXXXX.com             local;
      default                  reality;
  }

  upstream reality {
      server 127.0.0.1:8443;
  }

  upstream local {
      server 127.0.0.1:8444;
  }

  server {
      listen          443 reuseport so_keepalive=on;
      ssl_preread     on;
      proxy_pass      $backend;
      access_log      syslog:server=unix:/dev/log custom_stream_log;
  }

log_format custom_stream_log 'proxy: $status $protocol remote_addr $remote_addr, sent_to $upstream_addr';

  }
}

Нужно сначала объявить лог.
т.е. нужно строку

log_format custom_stream_log ...

поместить перед

  server {
      listen          443 reuseport so_keepalive=on;

del

Да, теперь стало писаться в access.log:

127.0.0.1 - - [13/May/2024:01:23:27 +0300] "\x16\x03\x01\x018\x01\x00\x014\x03\x03\x89\xEF\x9A\xD1\xC8\x8C6\x11\xFB\xE6(s!\x8D=\x90y\x07\x15q\xF1\x04\xE3\xED\xFB\xF1(\xC1{K\x80@ jY\x89\x03\xE3\xFE,\x13\x0C\x1Ek9\x86\xF1\xD9\x85~\xB7\xB0" 400 157 "-" "-" "-"
127.0.0.1 - - [13/May/2024:01:23:40 +0300] "\x16\x03\x01\x018\x01\x00\x014\x03\x03&\xE1\x84\xDFHOhR\xE5H\xB1(\xE7e\xFF\xD0\x8D\xD7\xFE'J\x22\x19\x7FHd\xFA\xEB\xE9[~* \xE5fW%\x04h\x8D" 400 157 "-" "-" "-"
127.0.0.1 - - [13/May/2024:01:24:13 +0300] "\x16\x03\x01\x018\x01\x00\x014\x03\x03D\x8Du\x83\x10\x19\xCB\xCF\x16\xF1z\x1D\x10b\xB2q\x1D\x95\x93Ap\xF0 \x0E$<\x131\x99\x87\xA7m \xAF-T +\xEBS\xEF\x872\xD6\xE8\x8C\xA3\x91\xBAn\xF08\xC6\x1AB\xBC\x0C@\xD4M\x94\xC3\xB3j\xED\x00>\x13\x02\x13\x03\x13\x01\xC0,\xC00\x00\x9F\xCC\xA9\xCC\xA8\xCC\xAA\xC0+\xC0/\x00\x9E\xC0$\xC0(\x00k\xC0#\xC0'\x00g\xC0" 400 157 "-" "-" "-"
127.0.0.1 - - [13/May/2024:01:24:32 +0300] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03M\xF4;(u\x1C\x96\xF2|C\x08M\xC4\xCA\x14\x8B\x03F\xDBT\x98\x10\xB9\x03y\x85U\xC0\xAE\x90<o De\xCB\x06\x9DZ\xF3\x91\xC86Z\xFAt\xBA8*\x04\xEFI_\x83\xF1\x01\xBC\xDF1\xFB\xE9\x96j\x85(\x00 \xEA\xEA\x13\x01\x13\x02\x13\x03\xC0+\xC0/\xC0,\xC00\xCC\xA9\xCC\xA8\xC0\x13\xC0\x14\x00\x9C\x00\x9D\x00/\x005\x01\x00\x01\x93jj\x00\x00\xFF\x01\x00\x01\x00\x00\x10\x00\x0B\x00\x09\x08http/1.1\x00\x17\x00\x00\x00+\x00\x07\x06**\x03\x04\x03\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00" 400 157 "-" "-" "-"

Логов самой прокси не видно, они должны начинаться с “proxy:”.
Можно, например, в терминале набрать journalctl -u nginx -f
И просто в браузере открыть свой сайт XXXXX.XXXXX.com, потыкать Ctrl+f5 (обновить с чисткой кэша), и смотреть, появятся ли записи типа:
proxy: 200 5653 TCP remote_addr 123.123.123.123, sent_to 127.0.0.1:8444
Таким образом убедившись, что запросы со своего внешнего адреса remote_addr улетают на слушателя nginx 127.0.0.1:8444, который, в свою очередь, может при знании нужного URL апгрейднуть соединение до ws и послать его в xray.

Первая строчка подключение по реалити, вторая - через CDN, третья - напрямую.

May 13 02:43:15 nginx: proxy: 200 TCP remote_addr XX.XX.109.166, sent_to 127.0.0.1:8443
May 13 02:43:26 nginx: proxy: 200 TCP remote_addr XX.XXX.113.11, sent_to 127.0.0.1:8444
May 13 02:44:02 nginx: proxy: 200 TCP remote_addr XX.XX.109.166, sent_to 127.0.0.1:8444

Ну пока все выглядит хорошо. В конфиге nginx, приведенном выше, тоже проблем не вижу.
Опять же, для верности, можно посмотреть, есть ли обмен с xray, хоть какой-то.

sudo tcpdump -i lo port 8889

И, если обмен есть, то смотреть логи xray, если обмена нет, то смотреть в nginx

Обмена никакого нет, когда я присоединяюсь на прямую или через CDN. Может в этом файле что-то не так, других-то я и не изменял /etc/nginx/conf.d/default.conf

server {
	listen 127.0.0.1:8444 so_keepalive=on;
        http2 on;


# ваш домен    
server_name XXX.XXX.com;

    #access_log  /var/log/nginx/host.access.log  main;

    # сюда можно положить какие-нибудь странички фейкового сайта, или использовать proxy_pass чтобы переадресовать запрос на другой сервер
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    #error_page   500 502 503 504  /50x.html;
    #location = /50x.html {
    #    root   /usr/share/nginx/html;
    #}

# путь к сертификатам, самоподписанным либо от Cloudflare
	ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
	ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

client_header_timeout 52w;
    keepalive_timeout 52w;
# замените TestChatGRPC на какую-нибудь секретную строку
	location /XXXXX {
		if ($content_type !~ "application/grpc") {
			return 404;
		}
		client_max_body_size 0;
		client_body_buffer_size 512k;
		grpc_set_header X-Real-IP $remote_addr;
		client_body_timeout 52w;
		grpc_read_timeout 52w;
		grpc_pass grpc://127.0.0.1:8888;
	}






    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}

# аналогично замените TestChatWS на какую-нибудь другую секретную строку
    location /XXXXX {
		if ($http_upgrade != "websocket") {
			return 404;
		}
		proxy_pass http://127.0.0.1:8889;
		proxy_redirect off;
	    proxy_http_version 1.1;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection "upgrade";
	    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_read_timeout 52w;
	}
}

Ну каких-то особых проблем я не вижу.
Но, во-первых, советую добавить ssl в заголовок, он вроде нужен, если идет работа с сертификатами.
У меня, например, так:

server {
        listen 127.0.0.1:8444 ssl http2 so_keepalive=on;

Во-вторых, интересно было бы посмотреть, что там nginx выдает при попытке соединения.
Предположим, что ws URL - 12345.

    location /12345 {
		if ($http_upgrade != "websocket") {
			return 404;
		}
               ...

Тогда ищем в журнале

journalctl -u nginx -r

Что-то вроде

nginx: 101 4076 "GET /12345 HTTP/1.1" "-" "Go-http-client/1.1"

Не обязательно именно такую строку, т.к. у меня формат логов изменен. Но обращать внимание на путь /12345 и статус. 101 - изменение протокола, т.к. http меняется на ws

DEL

Немного чище конфиг

server {
	listen 127.0.0.1:8444 ssl so_keepalive=on;
    http2 on;

	server_name XXX.XXX.com;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;


# путь к сертификатам, самоподписанным либо от Cloudflare
	ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
	ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

	location /XXXXX {
		if ($content_type !~ "application/grpc") {
			return 404;
		}
		client_max_body_size 0;
		client_body_buffer_size 512k;
		grpc_set_header X-Real-IP $remote_addr;
		client_body_timeout 52w;
		grpc_read_timeout 52w;
		grpc_pass grpc://127.0.0.1:8888;
	}

    location /XXXXX {
		if ($http_upgrade != "websocket") {
			return 404;
		}
		proxy_pass http://127.0.0.1:8889;
	    proxy_redirect off;
	    proxy_http_version 1.1;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection "upgrade";
	    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_read_timeout 52w;
	}

Кстати, у автора статьи он тоже отсутствует в конфиге. Я добавил ssl в заголовок, получаю следующее сообщение:
ошибка теста: Get "http://cp.cloudflare.com/": x509: certificate signed by unknown authority

Если разрешить небезопасное соединение TLS в nekobox:
ошибка теста: Get "http://cp.cloudflare.com/": context deadline exceeded

Получал ли ты сертификат для XXX.XXX.com? Сертификат от того же let’s encrypt, насколько я помню, распространяется только на домены и поддомены, которые явно указаны при запросе.

Ну и логи nginx бы посмотреть. В зависимости от настроек они падают либо в системный журнал, либо в /var/log/nginx

Мне let’s encrypt выписал Gcore, но кнопки скачать его нет.

Появилась такая запись при подключении с небезопасным TLS:
/var/log/nginx/access.log
127.0.0.1 - - [14/May/2024:00:43:45 +0300] "GET /XXXXX HTTP/1.1" 101 4 "-" "Go-http-client/1.1" "-"

Кстати, в журнале проскакивает какая то строчка, если что:
systemd[1]: nginx.service: Can't open PID file /run/nginx.pid (yet?) after start: Operation not permitted

Мне let’s encrypt выписал Gcore, но кнопки скачать его нет.

А это что за сертификаты тогда?

/etc/nginx/conf.d/default.conf

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

Самоподписанные?

У меня нет опыта работы с сертификатами, выданными cdn провайдерами. Но как вариант, попробуй настраивать всё на тот домен, который указал при настройке GCore. Если вводил не XXX.XXX.com, а XXX.com, то его и нужно прописать и в настройках nginx - server_name XXX.com;
и в настройках Nekoray тоже

127.0.0.1 - - [14/May/2024:00:43:45 +0300] “GET /XXXXX HTTP/1.1” 101 4 “-” “Go-http-client/1.1” “-”

Это хороший знак, осталось разобраться с сертификатами

Да, на XXX.XXX.com. Уже прописан у меня и в server_name и в nekobox.

Из статьи:
Также нам понадобится TLS‑сертификат, если у вас его еще нет. Как я уже говорил, при работе через CDN хватит самоподписанного сертификата (все равно его не будет видно снаружи, он используется только для связи между фронтендом CDN и вашим сервером), либо «внутреннего» сертификата от Cloudflare.

Выходит, эти сертификаты для сервера и CDN, а про прямое подключение ничего не говорится. В nekobox есть такое окошко, может туда чего прописать? Просто скопировать ключ из самоподписного сертификата завершается множественными ошибками.