将网站发布到公网从用途上来讲可分为自用和商用,本文仅针对流量不高,负载不大的自用场景总结一些已经实践过的无成本或低成本的可行方案。在自用场景下想要达成公网访问,简单的情形是网站服务部署在各类托管或PaaS平台,复杂的情形是部署在本地服务器上。对于静态网站如Hexo博客,部署到支持静态网页托管的云服务如Vercel既无成本(免费计划已足够)可用性又高;而对于一些轻量的有后端非静态的网站如Halo博客,部署到支持Docker的PaaS平台如Zeabur也能兼顾易用性和低成本。而想要将部署在本地服务器上的网站服务发布到公网则需要根据具体的网络资源情况选择合适方案。

方案分类

不同网络资源条件决定了对不同的发布到公网方案的适用性,需确认有无中转服务器(轻量云服务器)、有无公网IP,这里中转服务器指位于境内外的可稳定访问Cloudflare Tunnels的轻量云服务器,无root权限的虚拟主机如Serv00在本文方案下也可以作为中转服务器。一般云服务器都有公网IPv4地址,但未必能让本地服务器连通,而本地家宽下的服务器基本不可能获取公网IPv4,公网IPv6地址获取相对容易,故本文关于中转服务器有公网IP均指有本地服务器可联通的公网IP及对应端口,本地服务器有公网IP均指有可被外部访问的公网IPv6地址。以下是相应的几种情形:

  • 无中转服务器,本地服务器无公网IP:使用Cloudflare Tunnels(网络搜索关键词如Cloudflare 内网穿透即可获得教程本文不再赘述,但此方案高度依赖于Cloudflare Tunnels连接的稳定性,家宽条件下易出现断联,建议向运营商申请开通IPv6并换用有公网IP的方案,网络搜索关键词IPv6 开通即可获得教程,一般并不困难)
  • 无中转服务器,本地服务器有公网IP:使用IPv6 DDNS+Cloudflare CDN+指定端口反向代理+Origin Rules实现IPv4、v6双栈访问,如果追求更高可用性还可以结合SaaS回源加速的进阶方案
  • 有中转服务器,中转服务器有公网IP:使用frp的经典内网穿透方案,可结合SaaS回源加速
  • 有中转服务器,中转服务器无公网IP:使用Cloudflare Tunnels+frp的优化方案

准备工作

域名:需要一个可以托管至Cloudflare的域名,获取方案(须知免费的即是最贵的):

  • 可获取免费域名(网络搜索关键词如ClouDNS 域名即可获得教程):ClouDNSus.kgnic.ua
  • 低价的付费域名(网络搜索关键词如数字域名 .xyz即可获得教程):Spaceship

Cloudflare:注册登录Cloudflare账号,将域名托管至Cloudflare,并开通Zero Trust相关服务,了解Cloudflare Tunnels的安装使用(网络搜索关键词如Cloudflare 内网穿透即可获得教程)

1Panel:非必要,参考文档在本地服务器安装1Panel面板来方便运维管理。

中转服务器:非必要,可注册Serv00作为中转服务器(网络搜索关键词如Serv00 注册保活即可获取教程),或租一个轻量的服务器(推荐星空云,参考价格新加坡1c512m LXC小鸡,2r/月

DDNS+Cloudflare CDN+SaaS

本方案使用Cloudflare CDN实现IPv6站点的v4、v6双栈访问,配合IPv6 DDNS保持地址更新,并使用SaaS回源加速访问。

IPv6 DDNS+Cloudflare CDN

在1Panel的应用商店里安装ddns-go,并依照文档配置DDNS,一些Tips:

  • 可在1Panel-容器-ddnsgo容器-编辑-Command中将命令修改为-l :9876 -f 10 -cacheTimes 180,第一个参数为端口,第二个参数表示每十秒在本地检测IP是否变化,第三个参数表示每检测180次后和DNS服务器同步比对。如此设置可以让IPv6地址的变化尽可能及时地同步到DNS服务器
  • DNS服务商选择Cloudflare,IPv4下的是否启用需关闭,IPv6下的是否启用需打开
  • IPv6下的获取IP方式几种均可,建议使用通过网卡获取,一般IPv6地址都有多个,可通过匹配正则表达式进行指定
  • Cloudflare里一个子域名可以绑定多个AAAA也即IPv6地址,可以设置多个配置将每个IPv6地址都绑定至同一域名,例如(注意绑定多地址时需配置每个Domains参数中comment的值不同,具体的值是什么不重要,只要不同即可):
    • 配置1:获取IP通过网卡 eno1,匹配 @1,Domains subdomain:example.com?proxied=true&comment=@eno1
    • 配置2:获取IP通过网卡eno1,匹配@2,Domains subdomain:example.com?proxied=true&comment=@eno2
    • 配置3:获取IP通过网卡wlo2,匹配@1,Domains subdomain:example.com?proxied=true&comment=@wlo1
    • 配置4:获取IP通过网卡wlo2,匹配@2,Domains subdomain:example.com?proxied=true&comment=@wlo2

指定端口反向代理+Origin Rules

在1Panel的应用商店里安装OpenResty用于管理网站,注意安装时的HTTP(s)端口应分别指定为以下端口中的一个(80/443端口通常是屏蔽的,建议选择其余几个端口中的一对如2052/2053

  • HTTP:80、2052、2082、2086、2095、8080
  • HTTPS:443、2053、2083、2087、2096、8443

安装完成后需创建Acme账户、配置DNS账户、申请通配符证书,并为想要发布的网站创建反向代理如subdomain.example.com -> http://localhost:3000,记得勾选监听IPv6,并启用HTTPS

成功配置完DDNS和反向代理后访问https://subdomain.example.com:2053可以正常访问服务,但是不带端口号的https://subdomain.example.com无法正常访问,此时需要设置Origin Rules来将端口重写至设定的HTTPS端口2053

  • 在Cloudflare的网站-example.com-规则-Origin Rules中创建规则
  • 自定义筛选表达式,字段选择主机名,运算符选择等于,值填写subdomain.example.com(如果有多个子域名可点击Or按相同配置添加)
  • 目标端口选择重写到...,值为设定的HTTPS端口2053,点击部署即可

成功配置后即可在公网通过https://subdomain.example.com访问本地网站,但此时访问的延迟会较高,想要进一步优化延迟和访问速度,需要设置SaaS回源加速

SaaS回源加速

回源加速需要有一个托管在Cloudflare的额外域名extra.domain(此时主域名example.com可以不托管在Cloudflare),此时希望将本地网站发布到的域名依然是subdomain.example.com,但DDNS中配置的域名应该是如ddns:extra.domain?proxied=true&comment=@eno1

extra.domain下的配置:

  • DNS:cloudflare.extra.domain --CNAME--> cloudflare.182682.xyz 代理状态设为关闭,其中cloudflare.182682.xyz为任一优选域名
  • DNS:host.extra.domain --CNAME--> ddns.extra.domain ,避免直接将ddns.extra.domain设置为回退源时无法通过DDNS更新域名记录
  • SSL/TLS-自定义主机名:回退源设置为host.extra.domain
  • SSL/TLS-自定义主机名:添加自定义主机名subdomain.example.com
  • 规则-Origin Rules:添加主机名等于subdomain.example.com时重写到设置的HTTPS端口2053的规则,注意这里是反常识的,此处的Origin Rules在example.com的规则里设置是无效的,需要在extra.domain的规则里设置

example.com下的配置:

  • DNS:subdomain.example.com --CNAME--> cloudflare.extra.domain 代理状态设为关闭 ,用于通过自定义主机名中的主机名状态验证
  • DNS:_acme-challenge.example.com --CNAME--> exmple.com.xxxxxx.dcv.cloudflare.com,用于通过所有如subdomain.example.com一样的子域名在自定义主机名时的证书状态验证,其中xxxxxxextra.domain-SSL/TLS-自定义主机名-自定义主机名的DCV委派下查看

配置成功后在测速网站可观察到域名解析统计中有数十个不同的IP地址,这是优选域名的效果,它可以在不同地区选择各自最快的解析地址。需注意使用优选域名这种将自己的网站解析到非Cloudflare分配的IP地址是违反用户协议的,存在封号风险。

Serv00+frp+SaaS

这里选择Serv00作为中转服务器(其它有root权限的云服务器事实上配置起来更简单)需注意Serv00不同批次的IP并不都能在境内直接连通,这里使用的是s7的IP地址128.204.223.119

frp服务器端配置

下载解压frp,注意此处为0.61.1版本的适用于freebsd系统的包,如在其它系统上安装需在Releases下载对应包

1
2
3
4
# 下载 frp_0.61.1_freebsd
wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_freebsd_amd64.tar.gz
# 解压缩并赋权
tar -zxvf frp_0.61.1_freebsd_amd64.tar.gz && mv frp_0.61.1_freebsd_amd64 frp && chmod 777 frp

配置frp,将~/frp/frps.toml中的内容替换为以下内容

1
2
3
bindPort = 7000 # 修改为在Serv00中开放的一个TCP端口
vhostHTTPPort = 8080 # 修改为在Serv00中开放的另一个TCP端口
auth.token = "your_token" # 验证密钥,需设置为与客户端相同

编辑监测与重启脚本~/frprestart.sh,可在Corn Jobs编辑保活命令bash ./frprestart.sh,脚本内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/bash  

# 定义 frps 进程的命令
FRPS_CMD="./frp/frps -c ./frp/frps.toml"

# 定义 frps 的日志文件
FRPS_LOG="./frp/frps.log"

# 定义监控脚本的日志文件
MONITOR_LOG="./frp/frps_monitor.log"

touch $MONITOR_LOG
# 定义一个函数,用于检查 frps 是否正在运行
check_frps() {
# 使用 pgrep 查找 frps 进程
if pgrep -x "frps" > /dev/null; then
return 0 # 返回 0 表示进程正在运行
else
return 1 # 返回 1 表示进程未运行
fi
}

# 定义一个函数,用于启动 frps 进程
start_frps() {
echo "$(date) - 尝试启动 frps..." >> $MONITOR_LOG

# 确保 frps 日志文件存在
touch $FRPS_LOG

nohup $FRPS_CMD > $FRPS_LOG 2>&1 &
sleep 2 # 给 frps 启动留出一点时间
if check_frps; then
echo "$(date) - frps 启动成功。" >> $MONITOR_LOG
else
echo "$(date) - frps 启动失败。" >> $MONITOR_LOG
fi
}

# 定义一个函数,用于停止 frps 进程
stop_frps(){
echo "$(date) - 尝试停止 frps..." >> $MONITOR_LOG
pkill -f "frps -c ./frp/frps.toml"
sleep 2 #给进程停止留出时间
if ! check_frps; then
echo "$(date) - frps 停止成功。" >> $MONITOR_LOG
else
echo "$(date) - frps 停止失败。" >> $MONITOR_LOG
fi
}

# 主逻辑
if ! check_frps; then
echo "$(date) - frps 未运行。" >> $MONITOR_LOG
stop_frps
start_frps
else
echo "$(date) - frps 正在运行。" >> $MONITOR_LOG
fi

frp客户端配置

在1Panel中安装frpc应用,其中参数设置的服务端IP为Serv00的IP如128.204.223.119,服务端端口为Serv00中开放的bindPort端口如7000,密钥为设置的auth.tokenyour_token

安装完成后打开WebUI,在Configure的末尾加上(注意不是替换)如下格式的内容:

1
2
3
4
5
6
7
#http(s)实例
[[proxies]]
name = "your_app_name"
type = "http"
localIP = "127.0.0.1"
localPort = 3000 # 服务端口
customDomains = ["subdomain.example.com"]

SaaS回源设置

参照本地服务器上的SaaS回源配置,只需将host.extra.domain的部分改为host.extra.domain --A--> 128.204.223.119即可,另外Origin Rule在此方案下无需配置。

在Serv00的控制面板的WWW websites-Add new website增加Domain,并且在Advanced settings里将Website type改为Proxy,Proxy port改为服务端配置中设置的vhostHTTPPort8080

Cloudflare Tunnels+frp

本方案适用于无公网IP或公网IP无法被本地服务器连通的中转服务器,通过Cloudflare Tunnels将云服务器上frp服务端的TCP端口映射到指定域名,再在本地服务器通过该域名将TCP端口映射到本地端口并通过frp客户端连接,从而实现无公网IP的frp内网穿透连接。

中转服务器端配置

安装Cloudflare Tunnels并参照以下命令安装frp服务端

1
2
3
4
# 下载 frp_0.61.1_linux_arm64 注意这里是 arm 架构
wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_arm64.tar.gz
# 解压缩并赋权
tar -zxvf frp_0.61.1_linux_arm64.tar.gz && mv frp_0.61.1_linux_arm64 frp && chmod 777 frp

配置frp,将~/frp/frps.toml中的内容替换为以下内容

1
2
3
bindPort = 7000
vhostHTTPPort = 8080
auth.token = "your_token" # 验证密钥,需设置为与客户端相同

编辑监测与重启脚本~/frprestart.sh,两分钟检测一次,脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash  

# 定义 frps 进程的命令
FRPS_CMD="./frp/frps -c ./frp/frps.toml"

# 定义 frps 的日志文件
FRPS_LOG="./frp/frps.log"

# 定义监控脚本的日志文件
MONITOR_LOG="./frp/frps_monitor.log"

touch $MONITOR_LOG
# 定义一个函数,用于检查 frps 是否正在运行
check_frps() {
# 使用 pgrep 查找 frps 进程
if pgrep -x "frps" > /dev/null; then
return 0 # 返回 0 表示进程正在运行
else
return 1 # 返回 1 表示进程未运行
fi
}

# 定义一个函数,用于启动 frps 进程
start_frps() {
echo "$(date) - 尝试启动 frps..." >> $MONITOR_LOG

# 确保 frps 日志文件存在
touch $FRPS_LOG

nohup $FRPS_CMD > $FRPS_LOG 2>&1 &
sleep 2 # 给 frps 启动留出一点时间
if check_frps; then
echo "$(date) - frps 启动成功。" >> $MONITOR_LOG
else
echo "$(date) - frps 启动失败。" >> $MONITOR_LOG
fi
}

# 定义一个函数,用于停止 frps 进程
stop_frps(){
echo "$(date) - 尝试停止 frps..." >> $MONITOR_LOG
pkill -f "frps -c ./frp/frps.toml"
sleep 2 #给进程停止留出时间
if ! check_frps; then
echo "$(date) - frps 停止成功。" >> $MONITOR_LOG
else
echo "$(date) - frps 停止失败。" >> $MONITOR_LOG
fi
}

# 主逻辑 - 现在包含在循环中
while true; do
if ! check_frps; then
echo "$(date) - frps 未运行。" >> $MONITOR_LOG
stop_frps
start_frps
else
echo "$(date) - frps 正在运行。" >> $MONITOR_LOG
fi

sleep 120 # 等待120秒
done

在Cloudflare Tunnels的网页面板中添加公共主机名:

1
frps.example.com : TCP://localhost:7000

本地服务器配置

安装cloudflared客户端并通过以下命令检测端口映射是否成功:

1
cloudflared access tcp --hostname frps.example.com --url localhost:7000

成功后设置systemctl以后台启动:

1
sudo nano /etc/systemd/system/cloudflared-tcp.service

并将以下配置粘贴到文件内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=Cloudflared TCP Access Service
After=network.target

[Service]
ExecStart=/usr/local/bin/cloudflared access tcp --hostname frps.example.com --url localhost:7000
Restart=on-failure
#记录日志
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=cloudflared

[Install]
WantedBy=multi-user.target

启动服务并设置后台自启:

1
2
3
4
sudo systemctl daemon-reload # 重新加载 systemd 配置
sudo systemctl enable cloudflared-tcp # 设置开机自启
sudo systemctl start cloudflared-tcp # 启动服务
sudo systemctl status cloudflared-tcp # 查看服务状态

frp客户端配置同上文相似,在1Panel中安装frpc应用,其中参数设置的服务端IP为localhost,服务端端口为映射的端口7000,密钥为设置的auth.tokenyour_token

安装完成后打开WebUI,在Configure的末尾加上(注意不是替换)如下格式的内容:

1
2
3
4
5
6
7
#http(s)实例
[[proxies]]
name = "your_app_name"
type = "http"
localIP = "127.0.0.1"
localPort = 3000 # 服务端口
customDomains = ["subdomain.example.com"]

配置完成后在Cloudflare Tunnels的网页面板中添加自定义主机名:

1
subdomain.example.com : HTTP://localhost:8080

此时即可通过subdomain.example.com访问本地网站服务。使用Cloudflare Tunnels+frp的方案相比于在本地服务器上直接使用Cloudflare Tunnels发布网站,优点是受本地网络与Cloudflare Tunnels连接稳定性的影响更小,只需能够访问frps.example.com即可,服务不容易出现断联情况,更加稳定。