I’m self hosting multiple services at home, and in the past my main way of doing this has been to expose port 443 on my home internet, and use Traefik as an SSL terminator and proxy to route to multiple services with different subdomains. It works great, and in general I’d recommend that approach as a way of exposing services if you’re happy with the security implications of exposing a port from your home internet connection.

However, CloudFlare have a service called CloudFlare Tunnel, which works in a different way. The page on CloudFlare’s site explains this in a lot of detail, however as a very quick summary essentially CloudFlare becomes a ‘middle man’ between your home server and the internet. In this case, the home server makes a connection to the CloudFlare server. CloudFlare then use that connection opened from within your internal network to route requests, without needing to have a port exposed. It’s a very smart system, and it works in the same way that services such as ngrok and Inlets do (both which I’ve used in the past as well).

CloudFlare has great instructions for getting started with tunnels, however I had to do some extra steps for it to work with my Traefik config in the way I wanted.

1. Install CloudFlared

From the first section of the documentation, install on your machine. I went with Linux as I’m running on my home Ubuntu server currently.

2. Authenticate

Login to your CloudFlare account using this command:

cloudflared tunnel login

As I was using a headless server over SSH, I copied the URL into my browser and followed it that way.

3. Create a tunnel

This is super simple. Create a tunnel with the name you want. In this example I’ll call it tunnel1 - remember what this is as you’ll need it later.

cloudflared tunnel create tunnel1

4. Create configuration

This is where I needed to customise my configuration for my use cases.

As I’m hosting multiple services on one machine, via multiple subdomains, I wanted to make all of those work over the tunnels. I also wanted to allow my internal network to continue working correctly (i.e. if I go to a URL internally, the network traffic doesn’t leave my network. This is achieved with custom DNS entries on my internal PiHole servers to route traffic to my Traefik host). So to do that, I needed to route the traffic from the tunnel through Traefik.

To achieve this, I had to work out how to allow the tunnel to respect my hostname settings as well as allowing for my internal certificates (which are generated by LetsEncrypt via Traefik).

So lets build the file up.

Firstly, we need to set the tunnel name (from the last step) and the credentials file. Your credentials file should have been created when you logged in, and that’s the file you should reference in your file in the .cloudflared folder, which will probably be in your users home folder.

tunnel: tunnel1
credentials-file: /home/jamie/.cloudflared/<randomguid>.json

Next, you want to setup some ingresses. With my configuration, I want multiple hostnames through one tunnel. This is surprisingly flexible. The way it works is that it’ll go through the list of ingresses for each request received from top to bottom. Initially we need an ingress block with a ’terminating’ service at the bottom.

ingress:
  - service: http_status:404

Then in the ingress block, I want to add services. This is where my setup gets complicated.

Lets say I’m hosting a service over HTTPS at the url a.roos.click. There are a few options that are set in my service over and above what you might normally see.

ingress:
  - hostname: a.roos.click # This should match the hostname you want your request to come from on the internet.
    service: https://localhost:443 # This is where your want your request to 'go'.
    originRequest:
      originServerName: 'roos.click' # This allows my local certificate with roos.click as the hostname to be used to terminate the connection without issues. This is being setup via Traefik
  - service: http_status:404
  • hostname is the hostname you want your request to come from on the internet.
  • service is the destination. For me, I set this to https://localhost:443. This is my Traefik instance internally. So Traefik will inspect the Host header on the requests and determine which of my internal services to surface.
  • The originRequest section allows for configuration of how CloudFlare sends requests to each service. There is lots of detailed documentation on this here.
    • I then use originServerName. This setting is used to set the expected hostname from the origin server certificate. In my case, it would be roos.click (as I am using a wildcard certificate where the hostname is roos.click and an alternate subject is *.roos.click).

I then define multiple in one file for multiple endpoints. So my configuration file looks a bit like this:

tunnel: tunnel1
credentials-file: /home/jamie/.cloudflared/<randomguid>.json

ingress:
  - hostname: a.roos.click
    service: https://localhost:443
    originRequest:
      originServerName: 'roos.click'
  - hostname: b.roos.click
    service: https://localhost:443
    originRequest:
      originServerName: 'roos.click'
  - hostname: c.roos.click
    service: https://localhost:443
    originRequest:
      originServerName: 'roos.click'
  - service: http_status:404

5. Define the tunnel routes

Once you set services up, you need to route the tunnel. Run the below command for each hostname you want to route through your tunnel.

# cloudflared tunnel route dns <tunnel name> <hostname>
cloudflared tunnel route dns 'tunnel1' a.roos.click

For me, I then setup 2 more for example configuration file above:

cloudflared tunnel route dns 'tunnel1' b.roos.click
cloudflared tunnel route dns 'tunnel1' c.roos.click

6. Install as service

Install the service:

sudo cloudflared service install

7. Copy your configuration file

In my case, I am storing my file in source control. When I make changes I run a small script that looks like this from the root of my git repo.

# Copy the configuration from my Git repo to /etc/cloudflared/ as a file called config.yaml
sudo cp ./cloudflared/home_config.yaml /etc/cloudflared/config.yml
systemctl restart cloudflared

Conclusion

In conclusion, using CloudFlare tunnel to expose services to the internet means you can expose services without worrying about exposing ports directly on your home router to the internet. This tutorial is working well for HTTPS traffic for me, but CloudFlare appears to support many other protocols via this service. I may explore those in future as well.