Supercharge Your Home Cluster Using Cloudflare Tunnel

Mar 28, 2025

I'm a big fan of self-hosting and DIY. Since writing my previous blog post about my self-hosting journey, I have learned some exciting new things that I want to share it with you. First, I’ll explain my initial server setup. Then, I’ll discuss why I looked for an alternative, and finally, I’ll show how Cloudflare Tunnels helped me achieve my goals.

The Problem

If you are like me and you are hosting your website on your own home-cluster, there is some configuration you have to do to ensure you are not exposing your devices to the internet insecurely. Secondly, you will soon realize your home-cluster is not accessible in the same way inside your home (private network) as someone outside your network.

Initial Setup

My home-server runs on Proxmox and it exposes a lightweight Alpine LXC (Linux Container) to handle external traffic. I’ve deliberately disabled SSH on this container for extra security; I can only connect to its TTY via the Proxmox console. Instead of port-forwarding the services I want to expose one-by-one, I deliberately placed that container behind a DMZ, so I don't need to configure a port-forward everytime I need one (Shout-out to Xfinity for making port forwards extra difficult).

I currently host many things in my home-cluster, some of the applications run on a Kubernetes cluster and some of them run as standalone docker images because I was lazy to move them. I have my Blog, a Fresh RSS instance to manage my RSS subscriptions, a generic-purpose PostgreSQL instance to collect data from experiment runs for research projects, a Minecraft server to play with my friends, a Grafana dashboard to visualize different kinds of data and set various alerts, such as SSL certificate expiration of my website, an influx DB to collect sensor data from my house and many more.

My main Kubernetes cluster runs on MicroK8s, it has a MetalLB on front and I can setup ingress rules to forward traffic to different applications. However this is not enough on its own, because there are other applications outside kubernetes that I need to expose. Therefore I have decided to put everything behind HAProxy.

defaults                  
  mode http               
  timeout client 60s                  
  timeout connect 30s                      
  timeout server 60s                       
  timeout http-request 60s                       
                                                 
frontend .dogac.dev                              
  mode http                                      
  bind :443 ssl crt /root/haproxy/all.pem      
                                                 
  acl is_freshrss hdr(host) -i freshrss.dogac.dev
  use_backend fresh-rss if is_freshrss            
                                                  
  acl is_blog hdr(host) -i blog.dogac.dev         
  use_backend ghost-blog if is_blog               
                                                  
  acl is_grafana hdr(host) -i grafana.dogac.dev   
  use_backend main-cluster if is_grafana          
                                                  
  acl is_healthcheck hdr(host) -i health.dogac.dev
  use_backend main-cluster if is_healthcheck
                                       
  acl is_otp hdr(host) -i otp.dogac.dev
  use_backend main-cluster if is_otp                           
                                                               
  default_backend ghost-blog                                   
                                                               
                                                               
backend fresh-rss                                              
  mode http                                                    
  option forwardfor                                            
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  server container-master 10.0.0.X:X                     
                                                               
backend ghost-blog                                             
  mode http                                                    
  option forwardfor                                            
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  server container-master 10.0.0.X:X                      
                                                               
backend main-cluster                                           
  mode http                                                    
  option forwardfor                                            
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  server main-cluster 10.0.0.X:X ssl verify none
                                     
frontend psql-fe              
  mode tcp                           
  bind :X                        
  default_backend psql-be     
                                     
backend psql-be               
  mode tcp                           
  server psql 10.0.0.X:X

frontend minecraft-server            
  mode tcp                           
  bind :X                        
  default_backend minecraft-sv       
                                     
backend minecraft-sv                 
  mode tcp                           
  server game-server 10.0.0.X:X

By using this configuration, I am able to forward traffic coming from different sub-domains to different applications and potentially to kubernetes. Just to make everything extra secure, I did not forward unknown domains / subdomains to kubernetes, so I wouldn’t accidentally expose something.

I still had a couple of additional issues,

  1. I don't own a static IP address
  2. SSL certificates rotate every 3 months
  3. I expose my home IP address directly to the domain provider

For number 1, I used DDClient to automatically update my IP address to PorkBun domain provider regularly. As I don't provide any availability SLAs for my personal website and my IP address doesn't change often, it works fine.

For number 2, I have created a small script to automatically update my HAProxy certificates from Porkbun and I want to share with you. I ran this script a week before my SSL certs expired (my Grafana instance reminds me). Alternatively I could have scheduled this to be a weekly job.

#/bin/ash

set -eo pipefail

apikey=X
secretapikey=X     
domainname=X

resp=$(curl -s -X POST https://api.porkbun.com/api/json/v3/ssl/retrieve/$domainname -d "{\"secretapikey\": \"$secretapikey\", \"apikey\": \"$apikey\"}" | jq)

result=$(echo $resp | jq -r '.status')

if [[ "SUCCESS" != "$result" ]]; then
    echo "Not successful result: $resp"
    exit 1                             
fi        
  
chain=$(echo $resp | jq -r '.certificatechain')
privatekey=$(echo $resp | jq -r '.privatekey') 
                                              
mv all.pem old.pem 2>/dev/null
                              
echo "$chain" >> all.pem
echo "$privatekey" >> all.pem
                             
echo "Done!"

For number 3, I did not have much option with my current setup. As far as I know, Porkbun doesn't have a direct way to secure my IP address and I don't want to pay monthly for a proxy server.

Access from Home

My ISP and router doesn't allow NAT Loopback, meaning that I can't access my own network using its external IP while I am in the internal IP. You might ask, why do I need that? For example when I try to visit my website while I am at home, I can't access it because its domain name resolves to my external IP and my router doesn't allow it. There are a couple of ways around it but none of them are perfect,

  1. Change your router to support NAT Loopback. Firstly it is not guaranteed that it will work and secondly it requires additional maintenance cost and money.
  2. Update /etc/hosts. This technique works locally but you have to remember to update your hosts file every time you connect to an external network / internal network. Also, it needs to be configured per device. I am not sure if there is an equivalent way for my iPhone for example. Also you might face SSL certificate issues.
  3. Update Router's DNS records. As I have stated before, I don't think it is possible in my case and I don't want to deal with the complexity of an additional DNS server.
  4. Use server IP directly. My LB forwards traffic based on domain names and some services are configured to only listen to those domains. Also I have to switch to the domain name when I am on an external network. I can't change the server address each time for every application I use, such as my RSS reader, NetNewsWire.
  5. Use a Proxy server. My motivation is to not pay for an additional server and maintain it. However, Cloudflare provides a free solution that you can use. Let's explore it.

Where Cloudflare Tunnels Shine

In the previous sections, I have explained why a Proxy Server increases the security of your home cluster and helps you federate access from internal and external network. So I started searching for a free proxy alternative, however you shouldn't really trust a free proxy server. Previously I have set my own proxy using Squid, however I wasn't happy with its performance with AWS's Lightsail solution. Even if you don't mind paying for an additional server you still have to maintain it.

After a careful investigation, I have found Cloudflare Tunnel. From Cloudflare's website,

Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address. With Tunnel, you do not send traffic to an external IP — instead, a lightweight daemon in your infrastructure (cloudflared) creates outbound-only connections to Cloudflare's global network

This seemed like the perfect solution for me. First of all, I followed their docs to move my DNS nameservers from Porkbun to Cloudflare to start using it.

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/bin/cloudflared
chmod +x /usr/bin/cloudflared
cloudflared --help

Later I have created a cloudflared tunnel and installed it as a service. The service makes sure my certificates are up-to-date and tunnel forwards to my latest external IP.

cloudflared tunnel create home-server
cloudflared tunnel route dns home-server *.dogac.dev
cloudflared tunnel route dns home-server dogac.dev
cloudflared service install

Initially I thought about replacing HAProxy with cloudflared's ingress configuration. However, I concluded that it would be a lot of effort and a step backward from my current setup. So instead, I have decided to forward all traffic coming to my domain directly to HAproxy without any additional configuration

tunnel: home-server
credentials-file: /root/.cloudflared/XXX.json

ingress:
  - service: http://localhost:8443

Also I had to change my HAProxy config now. I should delete the SSL certificate now as cloudflared handles the SSL automatically and I have switched from port 443 -> 8443 so the cloudflare tunnel can use the standard port 443 for HTTPS.

frontend .dogac.dev
  mode http
  bind :8443

  ...

Also when you run dig queries on my domain now, you will see that your home IP address is hidden and instead it shows cloudflare's IP addresses. Now I can also benefit from Cloudflare's analytics on top of Google Analytics

And most importantly, I am able to visit my website directly from its domain, dogac.dev without needing any extra configuration. This allowed me to configure all my devices to directly use the domain address no matter which network I am connected to.

Conclusion

Cloudflare Tunnel is a free and secure solution for hosting your home-server. The setup was pretty straightforward and it helped me secure my home-cluster while providing a federated access both from my internal network and other external networks. Cloudflare has many other free features that I didn't have a chance to explore yet. I recommend it for any hobbyist that has a home-cluster. Let me know what you think about this post, and feel free to share any recommendations for my home cluster.