NAT建站那些事

35天前 · 代码 · 152次阅读

是这样的,距离上次服务器迁移至腾讯云后兜兜转转又一年,马上就到了要续费的日子了。一直以为“良心云”真的很良心,没想到打开续费账单和活动页面才发现屏幕写满了几个大字——老用户与狗不得入内。

续费账单

新用户专场

无奈之下只能另谋高就。

需求分析

因为没有钱,所以需要精打细算。首先看看我的服务器都跑了些什么东西。

  • bitwarden(密码箱)
  • qinglong(挂机平台)
  • nezha(服务器探针)
  • homepod(个人api服务)
  • alist(onedrive目录程序,用来看番)
  • wiki.js(就是wiki)

突然发现,我之前一直被传统的一些建站的思路所束缚住了。我的博客被放在了景安的一台虚拟主机上,实际上我的服务器是不需要向外界提供什么服务的,最多就是我的note需要调用一下我的homepod api,甚至还不是直接访问。所以有没有一种可能,有没有一个独立ip对于我来说似乎没那么重要。

那么NAT就成了一个主要的思考方向,因为其不包含独立ip,也不需要支付昂贵的带宽费用,价格会比正常产品便宜很多。最后找到了雨云的宿迁NAT机子,1核1G20M下行甚至只需要12软妹币一个月。

太划算了8

出于性能原因考虑,还是拿下了标配版的NAT小机(-¥174.8 )

怎么踩坑

服务器只能转发10个端口到公网,去掉1个ssh,还剩9个。如果我给每个服务都开个端口转发出去的话还剩3个端口,还是绰绰有余的。万一以后还需要更多服务咋办,所以为了以后着想,还是合理规划一下吧。

通过nginx的反代,我们可以将我们的服务都通过nginx转发出去,就算是算上https也之占两个端口(80和443)。这样一来我们实际上需要的端口只有三个(22、80和443)。最终我们可以得到服务器内流量转发大概是这么样一个结构

大概长这样

开始踩坑

首先在端口转发的地方将我们的22、80和443端口转发出去,拿到外部端口之后连上服务器进行一些基础的配置,装nginx,docker什么的就先略过了。

监听哪里?(IPv4)

一般nginx的配置文件在一开头会写上监听哪些端口,我们拿到的外部443端口是2***7,那么我们的nginx配置文件中,除了要监听443和80,还需要额外监听这个2***7端口,就像这样

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl http2;
  listen 2xxx7 ssl http2;
  listen [::]:443 ssl http2;
  ssl_certificate /usr/local/nginx/conf/ssl/server.crt;
  ssl_certificate_key /usr/local/nginx/conf/ssl/server.key;
  
    
  # ...
}

这样子,我们就可以通过我们的https://xxx.com:2xxx7来访问我们的服务了。

http也是同理,额外监听一个对外的80端口即可。


这里再简单说一下Nginx配置文件中listenserver_name字段的一些机制。

Nginx是如何将我们通过域名和端口匹配到我们想要转发的服务的呢?

通过阅读nginx文档中 How nginx processes a request 这一篇时,我们可以知道,nginx会去寻找请求头中的Host字段,并将其与nginx配置文件中的server_name匹配,将端口与配置文件中的listen字段匹配,来寻找用户指定的配置。比如下面这次请求以及对应的nginx配置文件:

Host字段

server {
    listen 80;
    server_name blog.mitsuha.space;
    
    ....
}

Nginx通过请求头中的Host字段和http默认的80端口找到了我在nginx中配置的虚拟主机。那么问题来了,如果我的请求没有带上Host字段怎么办?在http的情况下,确实拿你没什么办法,但是在https下,我们可以通过SNI来解决这一问题,这个我们会在后面说到。在http的情况下,就算没办法判断转发的出口,也得有一个合适的兜底方案吧。Nginx提供了一个解决方案。

同样是在 How nginx processes a request 这篇文档中,我们可以看到这样子的说明

If requests without the “Host” header field should not be allowed, a server that just drops the requests can be defined:
server {
    listen      80;
    server_name "";
    return      444;
}

没错,我们只需要定义一个server_name为空的server配置就好啦!

在http下,没有了Host字段,Nginx就找不到对应的配置了。那么在https下又是怎么解决的呢?在 Server names 这篇文档中有提到,server_name字段其实不是单纯像上面一样是通过请求头中的Host来判断的,具体的顺序如下

  • during SSL handshake, in advance, according to SNI
  • after processing the request line
  • after processing the Host header field
  • if the server name was not determined after processing the request line or from the Host header field, nginx will use the empty name as the server name.

可以看到,Host字段只是在第三层的判断,而第一层则是https特有的SNI。Nginx文档给出的SNI的解释是这个样子的

A more generic solution for running several HTTPS servers on a single IP address is TLS Server Name Indication extension (SNI, RFC 6066), which allows a browser to pass a requested server name during the SSL handshake and, therefore, the server will know which certificate it should use for the connection. SNI is currently supported by most modern browsers, though may not be used by some old or special clients.

也就是说,浏览器可以在SSL握手的时候就将server name传递过去用于匹配nginx中的server_name字段。比如这次的请求

without Host

这一次我的请求头中就没有携带Host字段,但是nginx还是成功将我的请求转发到我的服务了。

简单的http转发

以wiki服务为例,wiki的容器跑起来后对外暴露了一个3000端口,那么我们可以在nginx的文件中这样写。

server {
    listen 80 http2;
    listen 443 ssl http2;
    listen 2xxx7 ssl
    server_name wiki.exia.xyz;
    
    location /  {
        proxy_pass  http://127.0.0.1:3000;
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这样一来我们就可以通过https://wiki.exia.xyz:2xxx7来访问我们的wiki服务了。

WebSocket转发

大部分情况下,普通的http转发已经足够了,可是如果我们需要使用ws或者wss的时候,可能又会出现一些问题。

oh no

这里也简单给出一份配置

location /ws {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_pass http://127.0.0.1:8008;
    proxy_http_version 1.1;
}

嗒哒~

wss联通

小结

实现了http、https和ws的反代之后,已经能满足我的所有需求了。如果只是自用服务的话,NAT是完全可以满足大部分需求了,最后配置出来和独立ip的小鸡相比也只是域名后面多了个端口号而已。

说在最最后:

其实我本来真的想直接ip+端口转发服务的,但是发现vaultwarden必须要上https才能工作,没办法只能来踩坑了qaq

👍 7

NAT nginx

还没有修改过

评论

贴吧 狗头 原神 小黄脸
收起

贴吧

  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡

狗头

  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头

原神

  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神

小黄脸

  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  1. 土拨许 35天前

    虽然不是很懂,但认真看完了,技能逐渐+1中。

    1. 季悠然 33天前

      [打call] 多少有些收获就好啦

  2. 伊藤 35天前

    很有启发的文章,谢谢~

    另外问下 用cf的argo能否实现类似效果?

    1. 季悠然 35天前

      刚刚看了一下,cf的argo其实和NAT的内网穿透是一样的,只不过argo直接一步到位由cf接管了,所以实现的效果是更好的。不过国内来说cf的效果可能要打点折扣啦~

目录

avatar

季悠然

寻找有趣的灵魂

134

文章数

2002

评论数

3

分类

好热啊

arknights!

然则天下之事,但知其一,不知其二者多矣,可据理臆断欤?

纪昀

421