Nginx 在 TCP/TLS 下通过 域名 / SNI 分流的几种方式

Author Avatar
空気浮遊 2023年02月07日
  • 在其它设备中阅读本文章

总之,迁移博客的计划正在日程上了。

问题

关于自建站点总是有几个很现实的问题。

例如我又想做图站、又想做博客、又想进行科学研究,我又想给每个图站和博客都单独配一个域名而不是路径(路径太长了不是吗?),但你只有一个服务器。

所以通过 HTTP 请求中的域名或者 TLS 中的 SNI 对流量进行分流就是个必要的课题。

通过域名分流的几种方案

现在基本所有的网页服务器标配 TLS 。所以就有以下两个分支选项出现:

  • Plan A: 某个前置软件负责 TLS 并处理分流,然后把 TCP 丢给 Nginx 继续处理分流。
  • Plan B: Nginx 负责 TLS 并处理分流,然后把流量原封不动地丢给上游进行处理。

理论上,方案 B 更具有可扩展性和可维护性,但如果某个前置软件声明“它们处理分流的效率更高”,或者你已经是方案 A 的模样却不想改到方案 B,则你也可以使用方案 A。

方案 A

既然丢给 Nginx 的已经是纯粹的 TCP 流量,那么流量包含的应该就是普通的 HTTP 请求。

如果你只是想把流量丢给几个网页服务器,按照如下方式配置即可:

server {
    listen 80;
    server_name _;
    return 301 https://$host$request_uri;
} # HTTPS 永久重定向
server {
    listen 11451; # 如果你开了 proxy_protocol 则往后面追加 proxy_protocol,h2同理
    server_name A.com B.com;
    ....
}
server {
    listen 11451;
    server_name C.com D.com;
    ....
}

如果你说,不,我想把 TCP 流量中包含的 HTTP Host Header 给取出来,然后再通过这个 Host 在 Nginx 的 Stream Proxy 中进行分流,那你可以参考 这篇文章 ,因为我没试过。

方案 B

如果是 Nginx 来处理 TLS 的话可能会更简单。

配置如下:

stream {

    map $ssl_preread_server_name $name {
        a.x.com a;
        b.x.com b;
    }

    upstream a {
        server 127.0.0.1:114;
    }

    upstream b {
        server 127.0.0.1:514;
    }

    server {
        listen 10.0.0.1:443;
        proxy_pass $name;
        ssl_preread on;
        # proxy_protocol on;
    }
}

http {
    ...
    server {
        listen 127.0.0.1:114;
        server_name a.x.com;
        ...
    }
    server {
        listen 127.0.0.1:514;
        server_name b.x.com;
        ...
    }
}

其中,ssl_preread on; 会允许 Nginx 读取 TLS 握手过程开始时的 SNI(即变量 $ssl_preread_server_name)并通过这个变量来进行上游的分流。

引用 / 扩展阅读

https://stackoverflow.com/questions/34741571/nginx-tcp-forwarding-based-on-hostname
https://serverfault.com/questions/1015880/extracting-http-host-header-from-nginx-stream-proxy
http://nginx.org/en/docs/http/server_names.html
Xiaomage's Blog - 使用 Nginx 进行 SNI 分流并完美和网站共存