Allow external traffic securely using Cloudflare Tunnels
Description
Self hosting things on the internet always comes with a set of risks. First, you need to setup your domain DNS to point to the IP address of your network. At this point, you are essentially exposing your network to the world. Providers such as Cloudflare try to hide your IP as they function like a reverse proxy if you have that turned on, but the point remains.
From there you need to then protect your local network by implementing things such as vlans, port forwarding, firewall rules, etc. However, you still need to create an opening for that traffic to get to your network and service. And this is typically handled through Firewall rules, which can be complicated.
Cloudflare Tunnels
And this is where Cloudflare Tunnels come to the rescue!
Without needing to open your firewall you can essentially create a secure tunnel to your network directly giving cloudflare access to the services you specify. And the cool thing is, you don’t even need to provide your IP address!
Since I’m no networking expert, I won’t go into the details of how this works, but you can read about it here: https://www.cloudflare.com/products/tunnel/
Setup
Configure Cloudflare
First, you need to enable Cloudflare tunnels.
- Login to Cloudflare
- On the left, click on
Zero Trust
- Then on the Zero Trust dashboard select
Network > Tunnels
- Click
Create a tunnel
- Select
Cloudflared
- Give a name of your tunnel. I usually name it the same as the host
- Click
Docker
- Copy the token in the setup code provided on that page. You only need the token.
Setup Docker
I want to run this in docker compose for all the benefits that come with doing so.
Copy my repo: https://github.com/nateleavitt/cloudflare-tunnel-docker
First you will want to rename the env.example file to .env. From there you will want to past in the token you created in Cloudflare above
1
2
# env.example file
TUNNEL_TOKEN=your-token-here
Another thing you need to configure is the Docker network that the service you want to open up is on. Do a docker network ls
on your host and you will be able to see which networks are created. By default, Docker creates a separate network for your service. So it’s usually named similar to the service that is running. So, make sure to update the network section in the docker-compose.yml
file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# docker-compose.yml
version: '3.9'
services:
cloudflare-tunnel:
image: cloudflare/cloudflared:latest
container_name: cloudflare-tunnel
restart: unless-stopped
env_file: .env
command: tunnel --no-autoupdate run
networks:
- proxy # network name of the service that you want to expose
networks:
proxy: # network for the service that you want to expose
external: true
Then do a docker compose up to start the tunnel.
1
docker compose up -d
From there you should be able to go back to Cloudflare and see a Healthy
status on the Tunnels
page. If you are still on the last page, click Tunnels
on the left, and your tunnel should be listed there!
Once that is running we will then need to add the host for the specific service in Cloudflare
Configure Your Hostname
On your tunnel click configure
> Public Hostname
> Add a public hostname
. Here is where you provide the public cname record to your service. So if you want to host a website at www
, enter that into the Subdomain
, then select the domain you want to use. Then here is where you provide the URL to your service. If you are using internal DNS, then this is easy! But you can also select localhost:port
of your internal service. This service address is local to the host you are running the docker-compose file above. So use whatever address this host can access your service locally.
So for example, if the service you wanted to expose was running on localhost:3000
of the same host that is running the cloudflare-tunnel container, that is the address you use.
Once you add this, you should be able to then enter the full domain and it should pull up your internal service without ever opening up any firewall ports or providing your IP Address!!!
Bonus Traefik config
I have a guide which goes through setting up Traefik on docker. I use this to secure everything with SSL on my internal network. You can find the guide here: https://n8bit.io/posts/traefik-ssl/
To setup the cloudflare tunnel to work with your internal reverse proxy, you need to point the network to the network of your proxy. With my Traefik setup I use the proxy
network, so that is what I use in my docker-compose.yml
file. Then in cloudflare I can use https
for my internal Hostname URL on Cloudflare. The only catch is that you need to add the Entrypoint
for the Traefik service.
So for example, if you have an internal service configured with Traefik using an internal domain name, you need to also configure the external name as an Entrypoint. From my Traefik guide, I have an example hello-world service. I need to add a logical OR statement to the rule
for the router. Here is what the updated rule would look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
http:
#routers
routers:
hello-world:
entryPoints:
- "https"
rule: "Host(`hello-world.local.your-domain.com`) || Host(`www.your-domain.com`)"
middlewares:
- default-headers
- https-redirectscheme
tls: {}
service: hello-world
#endrouter
#service
services:
hello-world:
loadBalancer:
servers:
- url: "http://ip-address:port"
passHostHeader: true
#endservice
So this service will host requests for the internal DNS name or the public name. It will serve both!!!