用户名
密码

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

orzlee
2021-04-13 / 28 评论 / 1,279 阅读 / 正在检测是否收录...

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配置发现有其他同学也有这样的问题,但是无从查起,不了了之!

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

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

评论 (28)

取消
  1. 头像
    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上去的直接忽略了……

          回复
  2. 头像
    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,所以您这篇文章帮助挺大的,再次感谢

        回复
  3. 头像
    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

          是的

          回复
  4. 头像
    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直接分流过去不行吗?

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

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

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

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

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

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

      回复
  7. 头像
    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进来,识别流量请求域名转发到指定的端口!

          回复
  8. 头像
    libesse
    Linux · FireFox

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

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

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

      回复
  9. 头像
    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

          这样也可以

          回复