Supercharge Your Home Cluster Using Cloudflare Tunnel
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,
- I don't own a static IP address
- SSL certificates rotate every 3 months
- 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,
- 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.
- 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. - 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.
- 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.
- 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.