Nginx SNI 分流(端口复用)使用Xray+VLESS+XTLS

Nginx SNI 分流(端口复用)使用Xray+VLESS+XTLS

orzlee
2021-04-13 / 35 评论 / 2,531 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2021年10月19日,已超过210天没有更新,若内容或图片失效,请留言反馈。

nginx-sni-xray-vless-xtls.png

前言

Xray已经出来一段时间了,之前一直使用VLESS+tls+ws方式科学上网,其实都够用,没什么问题。但是了解了下XTLS黑科技,再看看Xray性能测试,这货简直爆炸,性能是TLS的数倍。

其实之前折腾过一次,但是Xray要监听443端口(不监听也行,但是不完美啊),影响做站。虽然有解决方法,但是不够完整,我一不小心就跳坑里面去了。Xray已经带了SNI回落功能(教程看这里:通过 SNI 回落功能实现伪装与按域名分流),Nginx也带SNI功能,我还是觉得用Nginx的比较好,免得折腾Xray影响网站运行。

这里我不会写如何安装Xray,因为教程实在是太多了,嫌麻烦还有一键脚本:Xray_onekeyV2ray脚本别用这个作者的哈,特别是服务器有站点的!!!Xray脚本这个大佬的还不错)。

因为服务器上有几个站点,装完Xray后实现SNI分流,过程非常顺利,但是chrome上有问题,几个二级域名访问的内容全部和第一个访问的域名一样。

例如:我先访问 a.example.com 然后在访问 b.example.com,b.example.com 响应内容和 a.example.com 一模一样,然后再访问 c.example.com 还是和 a.example.com 一样。开chrome隐身模式直接访问 c.example.com 正常,然后访问 a.example.com 或 b.example.com 内容又全是 c.example.com 的(不一定会每次都这样,要看浏览器是否使用现有连接或服务器是否断开了之前的连接)。

这个问题折腾我很长时间,最后发现和HTTP/2有关,我所有站点不使用HTTP/2就会正常,只要有一个站点使用了HTTP/2,你先访问HTTP/2站点,那么后面的其他子域名都会变成它的内容。这个情况没有仔细测试证明,无法确定。

最开始我使用Xray的SNI也是这样,我还以为是Xray SNI的问题,之后换成Nginx使用SNI还是这样。chrome中有这个问题,用手机safari浏览器完全正常。

经过网上一番搜刮,终于找到了问题根源:通配符域名证书

首先你得了解什么是SNI,个人理解:SNI就是通过证书区分服务。多个二级域名使用通配符域名证书的话,相同的证书无法区分具体想访问的服务,因为它们的证书都一样。

用下人家的图(原作者文章:Multiplex TLS Traffic with SNI Routing):

wildcard-name-certificate-sni.png

*由于原始服务器10.0.3.2使用的TLS证书具有通配符名称 .example.com,因此Web浏览器可以建立服务器名称为 b.example.com 的 TLS连接,并对HTTP/2请求使用相同的连接到 a.example.com。这可能会在网站和应用程序中导致未定义的行为。**

因为HTTP/2会保持TCP连接,导致你访问了 a.example.com 后,在访问 b.example.com 的时候使用的同一个连接,SNI跟失效了一样,直接使用该连接访问之前的服务。我在Xray的调试日志中发现访问错误的域名没有获取到,是空的!

两个域名使用同一个连接(红框内是连接ID),可以很清楚的看到两个域名使用同一个连接,返回的内容也是一模一样:

chrome-dev-tools.png

解决办法两种:

  1. 不使用HTTP/2不确定是否HTTP2的问题,但是你可以试一试
  2. 不使用通配符域名证书,站点域名证书一个个申请。

配置Nginx SNI

使用Nginx首先得安装(如果服务器自带就不用折腾了),不会安装Nginx可以看我之前的文章:Ubuntu 安装nginx如果使用Xray_onekey脚本,那他已经帮你全部弄好了,你都不用折腾)。

nginx.conf配置追加:

stream {
    map $ssl_preread_server_name $orzlee {
        a.example.com a;
        b.example.com b;
        c.example.com c;
    }
    upstream a {
        server 127.0.0.1:60088; ### A站点监听端口,xray xtls伪装站点
    }
    upstream xtls {
        server 127.0.0.1:4443; ### 你的Xray端口
    }
    upstream b {
        server 127.0.0.1:60001; ### B站点监听端口,业务站点
    }
    upstream c {
        server 127.0.0.1:60002; ### C站点监听端口,业务站点
    }

    server {
        listen 443 reuseport;
        listen [::]:443 reuseport;
        proxy_pass  $orzlee;
        ssl_preread on;
        proxy_protocol on;
    }
    server {
        ### Xray XTLS
        listen 127.0.0.1:60088 proxy_protocol;
        proxy_pass xtls;
    }
}

a.example.com.conf配置(伪装站点):

server
{
    listen 80;
    listen [::]:80;
    server_name a.example.com;
    return 301 https://$http_host$request_uri;
}

server
{
    ### 伪造站点由Xray处理SSL
    listen 127.0.0.1:60003 proxy_protocol; ### xray http/1.1
    listen 127.0.0.1:60004 http2 proxy_protocol; ### xray http/2

    set_real_ip_from 127.0.0.1;
    real_ip_header proxy_protocol;
    port_in_redirect off; ###重定向去掉端口号

    server_name a.example.com;
    index index.html index.htm index.php default.php default.htm default.html;
    root /var/www/a.example.com;

    ...
}

b.example.com.conf配置(其他业务站点配置照葫芦画瓢):

server
{
    listen 80;
    server_name b.example.com;
    rewrite ^(.*) https://b.example.com$1 permanent;
}


server
{
    listen 127.0.0.1:60001 proxy_protocol ssl http2;

    set_real_ip_from 127.0.0.1;  ### 获取真实客户IP,不然全是127.0.0.1
    real_ip_header proxy_protocol;

    port_in_redirect off; ###重定向去掉端口号。不然类似 try_files nginx帮你重定向时,你浏览器上都带了60003这样的端口

    ssl_certificate b.example.com.cer; ### 证书换成你的
    ssl_certificate_key b.example.com.key; ### 证书换成你的


    server_name b.example.com;
    root /var/www/b.example.com;
    index index.php index.html index.htm;

    ...

}

配置Xray

服务端配置:

{
  "log": {
    "access": "/var/log/xray/access.log",
    "error": "/var/log/xray/error.log",
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 4443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "你的UUID",
            "flow": "xtls-rprx-direct"
          }
        ],
        "decryption": "none",
        "fallbacks": [
          {
            "dest": 60003,
            "xver": 1
          },
          {
            "dest": 60004,
            "alpn": "h2",
            "xver": 1
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "xtls",
        "xtlsSettings": {
          "alpn": [
            "h2",
            "http/1.1"
          ],
          "minVersion": "1.3",
          "certificates": [
            {
              "certificateFile": "b.example.com.cer 记得换证书",
              "keyFile": "b.example.com.key 记得换证书"
            }
          ]
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls"
        ]
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}

客户端看作者说明吧,值得一看:Outbounds 可用协议列表-VLESSflow仔细看看。

结语

本来折腾Xray的,结果Nginx折腾大半天。虽然掉坑里了,总算是爬出来了,受益匪浅!

通配符证书用起来省事,碰到坑了一下还不好找问题。就今天这个坑上网找都不知道怎么描述,基本上找不到相关问题,比较冷门。之前参考其他博主Nginx SNI配置发现有其他同学也有这样的问题,但是无从查起,不了了之!

遇到这个坑不长记性都难。

2
取消
扫码打赏
支付金额随意哦!

评论 (35)

取消
  1. 头像
    小笨蛋
    Windows 10 · Google Chrome

    为什么xray回落会到其他端口?上游也没分配啊,根本打不开给xray设置的简易网站,不是很理解这个跳转是怎么跳转的。

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ 小笨蛋

      不知道你怎么设置的。想实现什么效果!

      回复
      1. 头像
        小笨蛋
        Windows 10 · Google Chrome
        @ orzlee

        我是想着看看专门为xray搭建的简易网页(就是回落的地方),就是这个效果。
        PS:我不应该加个啊的语气,不像请教的语气2333.

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ 小笨蛋

          如果要简单回落安文档操作就可以实现,不用nginx,用xray监听440和80端口。如果还有其他站点按我的文章操作也是没问题的!

          回复
          1. 头像
            小笨蛋
            Windows 10 · Google Chrome
            @ orzlee

            来还愿了。您回复我当天就实验成功了,运行至今很稳定,没出差错。表情感谢大大表情

            回复
  2. 头像
    HelloWorld
    Windows 10 · Google Chrome

    请问一下,用上面的配置,nginx使用的是www.aaa.net和www.bbb.com,xtls伪装在www.aaa.net,访问的网站,证书没问题,但是访问www.bbb.com还是一样显示www.aaa.net的证书,已经禁用了所有http2的相关参数了,貌似nginx里面的certificate配置没生效似的,www.aaa.net的nginx配置里面压根没有certificate,都是xray里面处理的,按理www.bbb.com的证书和xray没啥关系吧

    stream {

    SNI map map $ssl_preread_server_name $backend_name { www.bbb.com bbb; www.aaa.net aaa; } # xray upstream vless { server 127.0.0.1:8443; } # xray 伪装站点 upstream aaa { server 127.0.0.1:10000; } # website upstream bbb { server 127.0.0.1:9090; } server { listen 443; listen [::]:443; ssl_preread on; proxy_protocol on; proxy_pass $backend_name; }

    server {

    xray vless listen 127.0.0.1:10000 proxy_protocol; proxy_pass vless;

    }

    }

    回复
    1. 头像
      orzlee 作者
      Windows 10 · Google Chrome
      @ HelloWorld

      HTTP2可能没有关系,文章我已经修改过了!

      你的证书是不是单个域名都申请了?伪装站点的证书在Xray配置里面配置!
      其实这个根本问题就是把通配符证书换成指定域名证书,然后在nginx其他站点中使用指定域名证书就可以解决问题!
      如果你是两个不同的域名证书不会引起这个问题的,但是一个证书发放给多个不同域名使用的话那就会出现这个问题!你可以打开站点看看证书是不是颁发给对应域名使用的,如果里面包含你多个站点域名的话这个证书就不行!

      回复
      1. 头像
        HelloWorld
        Windows 10 · Google Chrome
        @ orzlee

        感谢回复这么及时!
        2个不同的域名,但是分别申请的.a.net和.b.com的通配符证书,xray里面配置的.a.net的证书,nginx里伪装网站没有配证书,没有加ssl选项,另外一个站点单独配置的.b.com的通配符证书
        所以打开www.a.net时证书是xray里面指定的,但是打开www.b.com时还是*.a.net的证书……
        我也有点懵圈,我再仔细检查一下nginx配置文件,看看是否有哪里出错

        回复
        1. 头像
          HelloWorld
          Windows 10 · Google Chrome
          @ HelloWorld

          抱歉,问题解决了,不是nginx或xray的配置问题,是acme生成的证书拷贝到目的文件夹时用了append搞错了,第一次拷贝时把文件名敲错了,后面再次改正之后,脚本里面有个append模式……然后就一个文件里面混杂了2个key和cert,但是nginx只识别了前面那部分,append上去的直接忽略了……

          回复
  3. 头像
    DeletedAccount
    Windows 10 · Google Chrome

    想问一下伪装网站a.example.com的证书是在哪里配置的呢?只看到说它的SSl由Xray配置,但是没看到Xray里有导入a.example.com的证书?
    "certificates": [
    {
    "certificateFile": "b.example.com.cer 记得换证书",# 不需要在这前面加入a.example.com的证书么?
    "keyFile": "b.example.com.key 记得换证书"
    }
    ]

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ DeletedAccount

      xray伪装站点按照xray文档操作就好了,nginx是因为多个站用nginx相对熟悉方便!伪装站证书还是在xray配置!

      回复
      1. 头像
        DeletedAccount
        Windows 10 · Google Chrome
        @ orzlee

        哦哦,我刚去看了下nginx和xray的配置文档,明白什么意思了,谢谢
        主要是看下面那个问增加一个业务网址证书时候,是不是应该把c写在b后面有点晕了。最后还是只有伪装站点的证书写进xray配置文件由xray处理,其它所有站点nginx处理就行
        xray的文档里没写怎么用nginx做SNI,所以您这篇文章帮助挺大的,再次感谢

        回复
  4. 头像
    Cal
    Windows 10 · Google Chrome

    如果再有个c.example.com的业务网址,streamSettings里的certificates该怎么写?就直接把c的证书和b的写到一块去?不是很懂这点

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ Cal

      照着例子加就好了,证书写到站点配置里面,nginx.conf只是分流!

      回复
      1. 头像
        Cal
        Windows 10 · Google Chrome
        @ orzlee

        也就是说c的nginx配置照着b的写,然后xray的配置里certificates写成下面这个:

        "certificates": [
        {
        "certificateFile": "b.example.com.cer",
        "keyFile": "b.example.com.key"
        },
        {
        "certificateFile": "c.example.com.cer",
        "keyFile": "c.example.com.key"
        }
        ]

        我理解对吗?

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ Cal

          是的

          回复
  5. 头像
    loren
    Windows 10 · Google Chrome

    nginx.conf的SNI分流那里,是否可以使用不同的域名,例如伪装是xx.a.com,其他网站是y1.b.com,y2.b.com

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ loren

      这个都可以,只要域名解析在你的服务器就行!

      回复
      1. 头像
        loren
        Windows 10 · Google Chrome
        @ orzlee

        多谢老大。另外,还有一个问题,我同时安装了transmissionBT,装好以后的默认地址是http://ip:9001,没有用vless之前,我是可以用proxy_set_header,隐藏地址和端口,同时https加密。但现在我不知道怎么弄了,用80端口http访问,可以隐藏地址和端口,正常访问,但是上SSL,采用其他普通web网站相同的办法,就报错。

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ loren

          什么错?用SNI直接分流过去不行吗?

          回复
  6. 头像
    loren
    Windows 10 · Google Chrome

    非常感谢!!试遍了网上各种乱七八糟的教程,终于用这篇,完美解决!!楼主功德无量

    回复
  7. 头像
    ora
    Windows 10 · Google Chrome

    这个是您吗https://qoant.com/2021/05/xray-nginx-sni/,不是的话就抄袭的很搞笑了居然还搞了个许可协议:)

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ ora

      我写的居然还会被抄袭!!!无所谓了,搞这种事的人太多了!

      回复
  8. 头像
    Dage
    MacOS · Google Chrome
    upstream a { server 127.0.0.1:60088; ### A站点监听端口,xray xtls伪装站点 } upstream xtls { server 127.0.0.1:4443; ### 你的Xray端口 } server { ### Xray XTLS listen 127.0.0.1:60088 proxy_protocol; proxy_pass xtls; }

    上面这个没看懂啥意思。

    我现在做法是haproxy做ssl sni分流
    后端xray+nginx做站点及回落+caddy naive及伪装

    caddy做站点开销比nginx大,效率比nginx差

    回复
    1. 头像
      orzlee 作者
      Windows 10 · Google Chrome
      @ Dage

      不用upstream nginx转发xtls流量xray无法识别为代理,使用upstream是为了复用443端口让xray使用,更好的伪装xray。上面的配置是用nginx使用内部端口配置站点或者xray,upstream流量转发,达到80或者443端口复用的目的。

      回复
      1. 头像
        Dage
        MacOS · Google Chrome
        @ orzlee

        upstream a {}为啥不直接server 127.0.0.1:4443呢?
        为啥先转60088,然后60088转转4443?
        主要是这段没看懂

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ Dage

          一个是自己的网站,另一个是xray,有可能还有更多的网站或其他服务在一台服务器上,这个配置是区分其他服务的端口!不是你想象的转来转去!所有流量都是走80或443进来,识别流量请求域名转发到指定的端口!

          回复
  9. 头像
    libesse
    Linux · FireFox

    xray 的服务端证书配置是不是应该用 a.example.com.cer 的证书呀?表情

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ libesse

      xray也一样,无论是nginx还是xray都不能用通配符域名证书,其实问题是在浏览器上,只能服务端做处理兼容浏览器!

      回复
  10. 头像
    cielpy
    MacOS · Google Chrome

    你好,我遇到了相同的问题,https://github.com/fatedier/frp/issues/2181#issuecomment-867605135 请问关于这个问题有进一步的解决办法吗

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ cielpy

      你的问题和那个问题不一样,你的多站点错乱使用了通配符域名证书,他的问题是nginx加上代理协议会导致frp错误!你的问题只需要吧通配符域名证书换成单个域名证书就行了,所有站点都需要换成单域名证书!

      回复
      1. 头像
        cielpy
        MacOS · Google Chrome
        @ orzlee

        是的,我的问题和 issue 的问题不一样,但是和你的这篇博客的问题是一样的
        找到了些资料 https://levelup.gitconnected.com/multiplex-tls-traffic-with-sni-routing-ece1e4e43e56
        https://stackoverflow.com/questions/44162263/request-cached-when-using-http-2

        现在除了不用通配符证书好像没什么别的办法了,但是我要分流的网站用单个证书不太好维护,所以我换成了用 Nginx 反代 HTTP 了,不分流 HTTPS 网站了

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ cielpy

          这样也可以

          回复
          1. 头像
            远方
            MacOS · Google Chrome
            @ orzlee

            我找到不是办法的办法,我稍微整理了下贴出来。
            不知道格式会不会乱,所以上传到图床作为图片。
            解释一下,免得被认为是病毒、广告之类的

            在 map $ssl_preread_server_name 中使用 default 作为所有业务站点的中转。这样可以使用泛域名证书,同时所有业务站点也可以使用统一的SSL配置(包括证书)。而且不用每增加一个业务站点就在 stream 里增加上游后端;同时业务站点之间的访问不会导致内容一样。但是仅这样的话,回落站点和业务站点先后访问还是有问题。 给回落站点单独申请证书,并将证书配置到Xray中,同时也在回落站点的Nginx配置中配置单独证书的SSL。虽然回落站点不用配置也能SSL,但是配置上单独证书后,可以保证回落站点和所有业务站点的先后访问没有问题。 我算是Nginx小白,也不知道这样配置会不会有效率问题。
            代码:stream { map $ssl_preread_server_name $backend { x.domain.ltd xray; # xray回落站点 default https; # 正常的业务站点,除了 x.domain.ltd 之外,其他都分流到 https。 } upstream xray { # xray xtls伪装站点 server 127.0.0.1:4430; # 也可以配置Xray使用进程的方式,但是要保证Nginx和Xray权限一致 } upstream https { server unix:/dev/shm/https.sock; # 使用进程方式更有效率 } server { listen 443 reuseport; proxy_pass $backend; ssl_preread on; proxy_protocol on; } } http { set_real_ip_from unix:; # 从进程获取真实IP real_ip_header proxy_protocol; port_in_redirect off; # 重定向去掉端口号 # Xray回落站点 server { listen 80; # 可以单独建立强制跳转到HTTPS的虚拟站点,为示例简洁,统一放置。 listen 127.0.0.1:6001 proxy_protocol; # 回落 HTTP/1.1 listen 127.0.0.1:6002 http2 proxy_protocol; # 回落 HTTP/2 server_name x.domain.ltd; # 本来回落站点的SSL已由Xray处理,会自动支持的。 # 明确配置SSL,并统一使用其他业务站点一样的监听端口(进程) listen unix:/dev/shm/https.sock ssl http2 proxy_protocol; # 配置证书,用回落站点域名的独立证书 # 和正常的业务站点一样,也可以使用泛域名证书。但会导致先访问回落站点和后访问回落站点时的证书不一致。 ssl_certificate /usr/local/nginx/conf/ssl/x.domain.ltd.crt; ssl_certificate_key /usr/local/nginx/conf/ssl/x.domain.ltd.key; # 其他站点配置 ... } # 业务站点1 server { listen 80; # 可以单独建立强制跳转到HTTPS的虚拟站点,为示例简洁,统一放置。 listen unix:/dev/shm/https.sock ssl http2 proxy_protocol; server_name domain.ltd www.domain.ltd; # 证书位置 ssl_certificate /usr/local/nginx/conf/ssl/domain.ltd.crt; # 该证书包括了 domain.ltd 和 *.domain.ltd ssl_certificate_key /usr/local/nginx/conf/ssl/domain.ltd.key; # 其他SSL 配置 ... # 其他站点配置 ... } # 业务站点2 server { listen 80; listen unix:/dev/shm/https.sock ssl http2 proxy_protocol; set_real_ip_from unix:; server_name blog.domain.ltd; } # HTTP模块其他配置 ... }

            详细的解释和思路:https://pic.imgdb.cn/item/62190c8f2ab3f51d91b00c27.png

            PS:
            站长的这个教程对我启发很大,但我先看到的是盗文的那个,解决不了SNI问题后一直搜,搜到这篇原文。
            看到很多朋友都一样的困扰,我在摸索出来后就回来贴在这里了。
            如果站长觉得我这样贴图不合适,但内容有用,可以整理一下直接放在自己的站上的,不要客气。我又不弄博客之类的,链接都不用给我加。
            如果觉得要原文或是要删除,可以给我发个邮件,毕竟写了这么多。。。。

            回复
            1. 头像
              orzlee 作者
              Linux · Google Chrome
              @ 远方

              感谢评论,解决问题的方法永远不止一个,找到最适合自己的就好了!写这篇文章也就是目前对我来说最合适的方法了,也感谢你提供的其他思路!

              回复