Post

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!!!

This post is licensed under CC BY 4.0 by the author.