5 minutes
Exposing Self-hosted services via CloudFlare Tunnel
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 tohttps://localhost:443
. This is my Traefik instance internally. So Traefik will inspect theHost
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 beroos.click
(as I am using a wildcard certificate where the hostname isroos.click
and an alternate subject is*.roos.click
).
- I then use
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.