[{"content":"这个博客是今天下午搭起来的，过程里有个绕不开的问题：443 端口早就被占了。\n起因 我这台新加坡的小机器身兼数职：SoftEther VPN 中心服务器、几台设备的组网中枢、顺带跑点代理。其中 443 是 SoftEther 在监听——这不是随便挑的端口，而是给那些卡在 CGNAT 后面、UDP 不通的节点留的兜底通道：L2TP/IPsec 起不来时，自动降级到走 TCP 443 的 SSL 隧道。443 长得像正常 HTTPS，几乎不会被中间设备拦。\n现在我想在同一个 IP 上开博客。博客要 HTTPS，HTTPS 要 443。而一个端口只能被一个进程 bind。\n几条走不通的路 换个端口给博客，比如 8443。技术上没问题，但 https://linlook.live:8443 这种链接发出去就是个笑话，谁也不会记。\n把 VPN 挪走，让出 443。等于把兜底通道拆了——真到了需要它的网络环境下，那些节点就彻底连不上了。本末倒置。\n让 SoftEther 自己反代。它确实有点 HTTP 相关的功能，但那是为管理界面准备的，不是给你托管站点用的。\n真正的解法：按 SNI 分流 关键在于 TLS 握手的第一个包。\n客户端发出的 ClientHello 里有个 SNI 字段，写着\u0026quot;我要访问哪个域名\u0026quot;——而这部分是明文的，加密还没开始。也就是说，一个中间人在完全不解密、不持有任何证书的情况下，光读这个字段就能知道该把连接转给谁。\nnginx 的 stream 模块带了 ssl_preread，干的就是这件事：偷看一眼 ClientHello，把 SNI 提取成变量，然后按它做四层转发。\n于是架构变成这样：\n公网 :443 ──► nginx stream (ssl_preread) ├─ SNI = linlook.live ──► 本机 HTTPS 博客 └─ 其它 / 没有 SNI ──► SoftEther 配置本身很短：\nmap $ssl_preread_server_name $backend443 { linlook.live blog; default softether; } upstream blog { server 127.0.0.1:4443; } upstream softether { server 127.0.0.1:4445; } server { listen 443 reuseport; ssl_preread on; proxy_protocol on; proxy_pass $backend443; } 这里有两个让它成立的巧合，值得说清楚：\n一、VPN 客户端天生不带 SNI。 它们连的是 47.84.69.171 这个 IP，而 SNI 里放 IP 地址是不合规的，客户端根本不会发。所以所有 VPN 流量都自动落进 default 分支，一行特判都不用写。\n二、非 TLS 流量也能正确处理。 OpenVPN over TCP 有自己的帧格式，第一个字节不是 TLS 握手的 0x16。ssl_preread 发现这不是 ClientHello 就直接放行、不设变量，同样走 default。我特意构造了一段非 TLS 字节流打过去验证，分流日志显示它准确落到了 SoftEther 那一侧。\n改完之后，SoftEther 的监听器从 443 挪到本机的 44443，nginx 接管 443。VPN 客户端一个字都不用改，它们连的还是 IP:443，只是中间多了一跳转发。\n两个坑 真实 IP 丢在了第一跳 stream 转发是四层的，后端看到的源地址会变成 127.0.0.1。解决办法是 PROXY protocol——nginx 在转发时额外写一小段头部，告诉下游\u0026quot;真正的客户端是谁\u0026quot;。\n我第一版把 proxy_protocol on 加在了内部的中转 server 上。配置能跑，博客也正常，但访问日志里清一色全是 127.0.0.1。\n原因很简单：那一跳的源地址本来就已经是 127.0.0.1 了。真实 IP 在更前面的 443 那一层就丢了，你在下游怎么补都补不回来。PROXY protocol 必须由第一个知道真实 IP 的人写下来。\n改成在 443 的 server 上统一开启，再让 VPN 分支多走一个 listen ... proxy_protocol 的中转 server 把这个头剥掉（SoftEther 不认识它），问题就解决了。\n顺带一提，这个 bug 比看上去更讨厌：我的写作后台有个\u0026quot;连续失败 6 次锁 15 分钟\u0026quot;的登录限流，它是按 IP 计数的。如果所有请求的来源都是 127.0.0.1，那就等于全世界共用一个计数器——任何人试错几次，所有人一起被锁在门外。\n新 vhost 悄悄抢走了默认站点 我把博客的 server 块放进了 /etc/nginx/conf.d/。而 nginx 主配置里那个老的 server_name _ 默认站点，是在 include conf.d/*.conf 之后才定义的。\nnginx 的规则是：某个端口上第一个出现的 server 块，就是这个端口的默认站点（除非有人显式声明 default_server）。于是我的新 vhost 顺位成了 80 端口的默认站点，所有按 IP 访问、Host 对不上的请求，全被它 301 重定向到了博客域名。\n后果是那台机器上原本用 http://IP/shell/mesh.sh 分发的组网脚本挂了——严格说不算完全挂，curl -fsSL 会跟着跳转，最后还是能下到文件，但凡是不跟随重定向的调用方就直接拿到一个 301 空响应。这种\u0026quot;看起来还能用\u0026quot;的故障最烦人。\n修法是一行：给老的默认站点显式加上 default_server。\n收尾 整套东西跑起来之后：\n博客是 Hugo 生成的纯静态页面，nginx 直接吐文件，没有数据库、没有 PHP、没有后台可以被人打 证书用 acme.sh 自动签发和续期 VPN 的兜底通道原样保留，客户端零改动 多出来的开销，大概就是每个连接多一次内核态转发 一个端口，两种完全不同的服务，靠的只是握手时那个没加密的域名字段。\n顺便，这篇文章本身也是在这台机器上写完发出来的——写作后台就在博客的 /admin 下面，手机浏览器打开就能用。\n","permalink":"https://linlook.live/posts/nginx-sni-share-443/","summary":"443 已经被 SoftEther 占着，还想在同一台机器上开个 HTTPS 博客。靠 nginx 的 ssl_preread 按 SNI 分流解决，顺便记两个把我坑到的细节。","title":"一个 443 端口，同时跑 VPN 和博客"},{"content":"站点终于跑起来了 🎉\n这台机器同时还扛着 VPN 中枢和一堆自建服务，博客是用 Hugo 生成的纯静态页面，由 nginx 直接吐出来，几乎不占资源，也没有什么可以被打的后台。\n后面打算在这里记录一些：\n服务器和网络的折腾过程 用得顺手的工具 一些随手的想法 先立个 flag，慢慢更新。\n","permalink":"https://linlook.live/posts/hello-world/","summary":"\u003cp\u003e站点终于跑起来了 🎉\u003c/p\u003e\n\u003cp\u003e这台机器同时还扛着 VPN 中枢和一堆自建服务，博客是用 Hugo 生成的纯静态页面，由 nginx 直接吐出来，几乎不占资源，也没有什么可以被打的后台。\u003c/p\u003e","title":"博客上线了"},{"content":"这里是 Linlook 的个人站点。\n日常写一点：技术折腾、工具与效率、随手记录的想法。\n有事可以邮件联系我。\n","permalink":"https://linlook.live/about/","summary":"\u003cp\u003e这里是 \u003cstrong\u003eLinlook\u003c/strong\u003e 的个人站点。\u003c/p\u003e\n\u003cp\u003e日常写一点：技术折腾、工具与效率、随手记录的想法。\u003c/p\u003e\n\u003cp\u003e有事可以邮件联系我。\u003c/p\u003e","title":"关于"}]