Trojan 和 Nginx 共用 443 端口

日月星辰 发布在Programming

当前一台个人VPS上部署多个服务已是常态,不同服务也会绑定不同的域名,因此想要提供HTTPS服务,就需要共用443端口。如果装有Trojan,在共用443端口的时候还要注意配置。此文针对trojan和Nginx共用443端口配置过程做简要记录,以备遗忘。

Trojan默认工作在服务器443端口,直接对接入口流量。如果要与Nginx共用443端口,需要设计为统一流量入口,根据域名服务进行二次转发。如下图所示:

flowchart LR;
        A[Client] -->|HTTPS Request| B[Nginx] -->  C{Dispatch}
        C --> Trojan  & V2Ray  -->|Invalid Request| D[Default Page]
        C --> Web
Loading

利用TLS握手阶段的SNI信息将流量在4层进行转发。Nginx支持基于SNI的4层转发,也就是识别SNI信息,然后直接转发TCP/UDP数据流。该功能由ngx_stream_ssl_preread_module模块提供,Nginx默认不启用该模块,该模块使用stream,不是http

~ nginx -V
nginx version: nginx/1.20.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) 
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'

确认Nginx是支持的,下面直接进行配置。

user  nginx;

pid   /var/run/nginx.pid;

# 其他配置保持默认即可

# 流量转发核心配置
stream {
    # 这里就是 SNI 识别,将域名映射成一个配置名
    map $ssl_preread_server_name $backend_name {
        aa.metaprogramming.space web;
        trojan.metaprogramming.space proxy_trojan;;
    # 域名都不匹配情况下的默认值
        default web;
    }

    # web,配置转发详情
    upstream web {
        server 127.0.0.1:10240;
    }

    # trojan,配置转发详情
    upstream trojan {
        server 127.0.0.1:10241;
    }
    upstream proxy_trojan {
        server 127.0.0.1:10249;
    }
    server {
          listen 10249 proxy_protocol;
          proxy_pass trojan; # 转发给真正的服务
        }

    # 监听 443 并开启 ssl_preread
    server {
        listen 443 reuseport;
        listen [::]:443 reuseport;
        proxy_pass  $backend_name;
        ssl_preread on;
    }
}

http {
  server {
        listen 10240 ssl proxy_protocol;
            server_name xxx.metaprogramming.space;
            ssl_protocols TLSv1.2 TLSv1.3;
                ssl_certificate /etc/nginx/cert/xxx.crt;
                ssl_certificate_key /etc/nginx/cert/xxx.key;
                ssl_session_cache shared:SSL:1m;
                ssl_session_timeout 5m;
                ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
                ssl_prefer_server_ciphers on;

                location / {
                      proxy_pass http://127.0.0.1:8080;
                      proxy_set_header Host $http_host;
                      proxy_set_header X-Real-IP $remote_addr;
                      proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
               }
    }
}

trojan 服务配置为监听 10241端口即可。

在后端服务为了拿请求的real client ip使用Proxy Protocol协议进行通信,Nginx支持该协议,只需要在转发端和接收端配置上代理协议即可。所以在上述的转发层增加了proxy_protocol配置。由于Trojan不支持该协议,所以增加了一个中间层帮Trojan把协议去掉,从而保证其他服务能获取用户真实IP,又保证Trojan流量正常处理。

参考: