缘起
为了能够从公网访问我在自己服务器上搭建的网站和部署的服务,我之前的做法是:
- 使用动态域名解析服务(DDNS)来将我的公网IP地址和域名绑定起来。
- 然后使用Nginx配置反向代理,将域名指向我服务器上的服务。
- 最后用acme来获取SSL证书,实现HTTPS访问。
后来,为了将反向代理也容器化,我转向了Traefik,将第2步和第3步合并了起来。算是一个更加方便的解决方案。以上这些可以参考我之前发布的文章:
以上的解决方案已经算是比较好用了,但依然依赖于公网IP地址的稳定性,以及端口的开放情况。如果在公司或学校等网络环境下部署,可能会遇到IP地址不稳定或端口被封禁的问题。很长一段时间内,我都认为这个问题很难解决。
最近,我终于发现了一个比较好的解决方案:使用Cloudflare Tunnel。这个方案可以让你在没有公网IP地址的情况下,或者在端口被封禁的情况下,以及网络被防火墙限制的情况下,依然从公网访问你的服务。
前置条件
- 运行Linux系统的电脑 (作为私人服务器)
- 私有域名(详细操作请查看之前的帖子“个人网站的建立过程(一):购买个人域名并配置动态域名解析”)
- 了解容器化的基本概念,会用Docker或者Kubernetes(K8s或K3s)部署服务。
- 了解反向代理的基本概念和基本用法。可以参考“从公网访问个人网站——Nginx反向代理配置”或者“从公网访问个人网站(二)——Traefik反向代理配置”,里面有较为详细的介绍。
- 有一个Cloudflare账号,并且已经将你的域名添加到Cloudflare中。如果你的域名之前是由其他域名商提供的DNS服务,你需要将域名的DNS服务器切换到Cloudflare提供的DNS服务器。可以参考“将域名服务商从Hostinger迁移到Cloudflare”来了解如何将域名迁移到Cloudflare。
Cloudflare Tunnel基本概念
简介
Cloudflare Tunnel(之前称为Argo Tunnel)是Cloudflare提供的一项服务,它可以让你在没有公网IP地址的情况下,将你的服务暴露到公网。它通过在你的服务器上运行一个轻量级的代理程序,将你的服务通过Cloudflare的网络进行转发,从而实现从公网访问你的服务。
工作原理
Cloudflare Tunnel的工作原理可以参考官方文档,简单来讲是这样的,只要你的电脑能访问Cloudflare的DNS服务器,就可以建立一条从Cloudflare到你电脑的隧道。在声明将你的域名解析到这个隧道上之后,Cloudflare会将所有关于你的域名的请求通过这个隧道转发到和这个隧道连接的电脑上。同样地,你电脑上的服务也可以通过这个隧道将响应发送出去。这样就实现了从公网访问你的服务。
当用户在公网访问某个服务时,具体过程可参见下图:
- 客户在浏览器中输入你的域名,域名解析到Cloudflare的DNS服务器。
- Cloudflare的服务器将请求转发到你建立的Cloudflare Tunnel。这个Tunnel靠你电脑上的cloudflared程序来维持。
- cloudflared将请求转发给你设置的反向代理服务(如Traefik或Nginx)。
- 反向代理服务将请求转发到你电脑上的具体服务(如NextCloud、Jellyfin等)。
- 具体服务处理请求后,将响应通过上述反向的过程发送回给做出请求的客户。
可见,上述过程只要求你的电脑能够访问Cloudflare的服务器,而不需要公网IP地址或开放端口,也不管你的网络是否被防火墙限制。
Cloudflare Tunnel的使用
开启Cloudflare的Zero Trust服务
Cloudflare Tunnel是Cloudflare Zero Trust服务的一部分,因此你需要先开启Cloudflare的Zero Trust服务。
登录Cloudflare账号后,左侧菜单栏中找到“Zero Trust”,点击进入。
首次开启会让你选择一个套餐,对于我这个个人用户而言,免费套餐就够用了。填入信用卡的付款信息开启即可。
创建一个Tunnel
在Zero Trust页面,左侧边栏有一个Networks
选项,展开后里面有个Tunnels
选项,点击即可打开Tunnels管理页面:
你可以点击Create a tunnel
按钮来创建一个新的Tunnel,但这里不推荐这么创建,因为这样创建后还需要将配置挪到服务器上。我们选择在服务器上直接创建,步骤如下:
1. 在服务器上安装cloudflared
首先我们在服务器上安装cloudflared程序。cloudflared是Cloudflare提供的一个命令行工具,用于创建和管理Cloudflare Tunnel。具体安装方法参见官方文档。这里简单列一下:
在Debian系Linux系统(Ubuntu等)上:
1 2 3 4 5 6 7 8 9
# Add cloudflare gpg key sudo mkdir -p --mode=0755 /usr/share/keyrings curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null # Add this repo to your apt repositories echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list # install cloudflared sudo apt-get update && sudo apt-get install cloudflared
在RedHat系Linux系统(fedora等)上:
1 2
sudo dnf config-manager --add-repo https://pkg.cloudflare.com/cloudflared-ascii.repo sudo dnf install cloudflared
2. 在服务器上创建Tunnel
在安装好cloudflared后,我们首先需要登录一下Cloudflare账号。在服务器上运行
1
cloudflared tunnel login
会给你输出一个链接,复制这个链接到浏览器即可登录Cloudflare账号。登录后会让你选择你想要绑定到Tunnel上的域名,选择域名即可。
然后在服务器上运行
1
cloudflared tunnel create my-tunnel
其中
my-tunnel
是你要创建的Tunnel的名称,你可以根据需要修改。运行后会输出一个Tunnel的ID,记下来。同时,cloudflared会在你的服务器上创建一个记录这个Tunnel的JSON文件,位置一般在
~/.cloudflared/
目录下,文件名是Tunnel ID。这个文件很重要,里面存储了连接这个Tunnel所需的凭证。这时你可以登录Cloudflare网页端查看Tunnel是否创建成功。在
Zero Trust -> Networks -> Tunnels
页面,你应该能看到刚才创建的Tunnel:注意,这时我们只创建了这个Tunnel,还没有连接它,因此它处于
inactive
的状态。
3. 连接Tunnel
这里分别以Docker和k3s为例,说明如何将部署的服务连接到Tunnel。
Docker
前置条件:
这里假设
- 你已经安装了docker和docker compose,
- 使用Traefik来做反向代理,
- 并创建了一个所有容器共享的网络
traefik-net
。
具体过程可参考“从公网访问个人网站(二)——Traefik反向代理配置”。
创建cloudflared容器
在你的Docker项目目录下(假设是
~/docker
),创建一个新的项目文件夹cloudflared
,项目结构如下:1 2 3 4
cloudflared/ ├── docker-compose.yml ├── .env └── config.yml
docker-compose.yml
文件内容如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
version: '3.8' services: cloudflared: image: cloudflare/cloudflared:2025.5.0 container_name: cloudflared command: tunnel --config /etc/cloudflared/config.yaml run volumes: - ./config.yaml:/etc/cloudflared/config.yaml:ro - ${CREDENTIAL_FILE}:/etc/cloudflared/creds/credentials.json:ro restart: unless-stopped networks: - traefik-net networks: traefik-net: external: true
这里的
my-tunnel
是你之前创建的Tunnel的名称。CREDENTIAL_FILE
是你之前创建的Tunnel时生成的凭证文件的路径,通常是~/.cloudflared/<Tunnel ID>.json
。你可以在.env
文件中定义这个变量:.env
文件内容如下:1
CREDENTIAL_FILE=<User Home Path>/.cloudflared/<Tunnel ID>.json
除此之外,我们还需要一个配置文件
config.yml
:1 2 3 4 5 6 7 8
tunnel: li-tunnel credentials-file: /etc/cloudflared/creds/credentials.json metrics: 0.0.0.0:2000 no-autoupdate: true ingress: - hostname: "*.jinli.li" service: http://traefik:80 - service: http_status:404
- 这里的
hostname
是你要通过Tunnel访问的域名,这里我让所有以jinli.li
结尾的域名都通过这个li-tunnel
访问。 service
是指向Traefik容器的服务地址。注意这里的traefik:80
是指Traefik容器的名称和端口。
- 这里的
启动cloudflared容器 在
cloudflared
目录下运行以下命令来启动cloudflared容器:1
docker-compose up -d
这时你可以在
Zero Trust -> Networks -> Tunnels
页面看到Tunnel的状态变为active
,表示Tunnel已经连接成功:配置DNS记录
在Cloudflare的DNS管理页面,删除掉之前关于
*.jinli.li
的DNS记录。然后回到服务器命令行,运行
1
cloudflared tunnel route dns li-tunnel "*.jinli.li"
这条命令创建了一个wildcard DNS记录,将所有以
jinli.li
结尾的域名都指向这个Tunnel。当然,你也可以只指定某个具体的域名,例如:
1
cloudflared tunnel route dns li-tunnel "nextcloud.jinli.li"
或者在Cloudflare的网页端DNS管理界面来创建DNS记录,选择
CNAME
类型,指向<Tunnel ID>.cfargotunnel.com
,其中<Tunnel ID>
是你之前创建的Tunnel的ID。具体可参见官方文档。但不知为啥,我这么创建后依然无法访问域名,上面用命令行创建的就没问题。注意:如果你之后不需要再用命令行来创建DNS记录了,那么在服务器用包管理工具安装的这个cloudflared程序就没啥用了,你可以将其卸载。之前创建的Tunnel将由Docker里容器化的cloudflared程序维护。
测试访问
现在你就可以通过浏览器访问你的域名了,例如
https://nextcloud.jinli.li
。如果你之前已经部署了NextCloud服务,并在Traefik中配置反向代理,那么就可以直接访问了。
K3s
前置条件:
这里假设你已经安装了K3s,并且使用Traefik作为Ingress Controller。具体过程可参考“Homelab(1):使用Kubernetes(K8s)或K3s自建家庭集群”。
将Tunnel凭证secret化
之前创建Tunnel时cloudflared生成的凭证文件保存在
~/.cloudflared/<Tunnel ID>.json
,我们需要将这个文件转换为Kubernetes的Secret。在服务器上运行以下命令:
1
kubectl create secret generic cloudflared-credentials --from-file=credentials.json=<User Home Path>/.cloudflared/<Tunnel ID>.json
其中
<User Home Path>
是你的用户主目录路径,<Tunnel ID>
是你之前创建的Tunnel的ID。创建cloudflared Deployment和Service
所需的manifest文件如下:
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
apiVersion: apps/v1 kind: Deployment metadata: name: cloudflared spec: selector: matchLabels: app: cloudflared replicas: 1 template: metadata: labels: app: cloudflared spec: containers: - name: cloudflared image: cloudflare/cloudflared:2025.5.0 args: - tunnel - --config - /etc/cloudflared/config/config.yaml - run livenessProbe: httpGet: path: /ready port: 2000 failureThreshold: 1 initialDelaySeconds: 10 periodSeconds: 10 volumeMounts: - name: config mountPath: /etc/cloudflared/config readOnly: true - name: creds mountPath: /etc/cloudflared/creds readOnly: true volumes: - name: creds secret: secretName: li-tunnel-credentials - name: config configMap: name: cloudflared items: - key: config.yaml path: config.yaml --- apiVersion: v1 kind: ConfigMap metadata: name: cloudflared data: config.yaml: | tunnel: li-tunnel credentials-file: /etc/cloudflared/creds/credentials.json metrics: 0.0.0.0:2000 no-autoupdate: true ingress: - hostname: "*.jinli.li" service: http://traefik.kube-system.svc.cluster.local:80 - service: http_status:404
配置的含义跟Docker里的类似,这里不再赘述。
部署cloudflared
将上述manifest文件保存为
cloudflared.yaml
,然后在服务器上运行以下命令来部署cloudflared:1
kubectl apply -f cloudflared.yaml
这时你可以在
Zero Trust -> Networks -> Tunnels
页面看到Tunnel的状态变为active
,表示Tunnel已经连接成功。如果没有,那么你需要检查cloudflared是否部署成功。
配置DNS记录与测试访问
这两步跟上述在Docker中使用cloudflared的步骤(第3步和第4步)一样。