When I set up my homelab, I hit the same wall that I think everyone hits.
The server was running. Proxmox was installed. My production, testing and misc VMs were up. Everything worked perfectly, as long as I was sitting at home on the same Wi-Fi.
But the whole point was to use it from anywhere. From uni, from a cafe, from my phone. And eventually, to put real apps on the internet so other people could use them.
The old answer to this was always the same: get a static IP from your ISP, log into the router, forward some ports, and hope nobody finds them. I didn't want to do any of that. And it turns out I didn't have to.
This post is about the two tools that solved it for me, Tailscale and Cloudflare Tunnel, and how they actually work underneath. Because the first time you use them, it really does feel like magic.
The problem with home internet
To understand why these tools are so nice, it helps to understand why home hosting is normally painful.
Your IP address changes. Most home internet plans give you a dynamic public IP. Your router might get a new one after a restart, an outage, or just because your ISP felt like it. If your app lives at an IP address, and that address keeps moving, nobody can find it.
Sometimes you don't even have your own public IP. Many ISPs use something called CGNAT (carrier-grade NAT), where lots of customers share one public IP. In that case, port forwarding doesn't work at all, because the port you open on your router never sees traffic from the internet in the first place.
Your router blocks incoming connections. This is a good thing! By default, your home router lets your devices make connections out to the internet, but blocks random connections coming in. That's why your laptop can browse websites, but a stranger can't connect to your laptop.
Port forwarding punches a hole in that wall. The traditional fix is to tell the router "send everything on port 443 to my server". It works, but now your server is directly reachable by the entire internet. Bots scan every IP address constantly, and an open port gets found within minutes. If what's behind that port has a weakness, someone will try it.
And on top of all that, I didn't want my setup to depend on my router at all. If I move house, or change ISP, or my router gets replaced, I want the server to just keep working without me logging into anything.
The trick both tools use: connect out, not in
Here's the idea that makes everything click. Both Tailscale and Cloudflare Tunnel turn the problem around.
Instead of the internet connecting in to your server (which your router blocks), your server connects out to something (which your router happily allows). Once that outbound connection exists, traffic can flow back through it.
That one change means:
- No static IP needed. It doesn't matter what your public IP is, or if it changes, because nobody needs to know it.
- No port forwarding. Nothing new is opened on the router.
- It works behind CGNAT, because outbound connections work fine there.
- It works the same at any address. Plug the server in somewhere else, and it reconnects on its own.
That's the whole magic. Everything else is detail. But the details are interesting, so let's go through them.
Tailscale: my private network, everywhere
I use Tailscale for private access. That means me, and only me, getting to Proxmox, SSH and the VMs from anywhere.
Tailscale creates what they call a tailnet: a private network made up of your own devices. My laptop, my phone, the Proxmox host and the VMs are all on it. Each device gets its own stable private IP address (in the 100.x.y.z range) and a name. So I can open the Proxmox web UI from a cafe the same way I would at home, just using its Tailscale name instead of a local IP.
Setting it up on a Linux machine is honestly two commands:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
The second command gives you a link. You log in with your account, approve the device, and that's it. It's on your network.
How it actually works
Under the hood, Tailscale is built on WireGuard, a modern, fast and well-reviewed VPN protocol. What Tailscale adds on top is everything that normally makes WireGuard annoying to set up by hand.
There are two parts to it:
1. The coordination server (the control plane). When a device joins your tailnet, it creates its own private key, which never leaves the device. It sends only its public key to Tailscale's coordination server. The coordination server's job is to tell each of your devices about the others: "here's your laptop's public key, here's roughly where it can be reached". It's like a phone book.
2. The direct connection (the data plane). Once your devices know about each other, they try to connect directly, device to device. This is where the clever part is. Both devices are probably behind routers that block incoming connections. So Tailscale uses NAT traversal: both sides reach out at the same time, and each router sees the other's traffic as a reply to something it sent, so it lets it through. It sounds like a hack, and it kind of is, but it works most of the time.
When a direct connection really isn't possible (some strict networks block it), traffic goes through Tailscale's relay servers, called DERP. Even then, the traffic is still encrypted end to end with WireGuard. The relay only passes along encrypted packets it can't read.
The important part: my actual traffic doesn't go through Tailscale's servers when a direct connection works, and Tailscale can't read it either way. The coordination server only handles keys and connection info.
My laptop (cafe Wi-Fi) Homelab (home internet)
| |
|---- "who's in my tailnet?" ---> Coordination server <--- same ---|
| |
|<========= direct WireGuard tunnel =========>|
(encrypted, device to device)
Why Tailscale feels more secure than port forwarding
This is the part that made me really like it.
Nothing is exposed to the internet. My Proxmox web UI and SSH aren't listening on any public port. If you scan my home IP, there's nothing to find. You can't attack a login page you can't reach.
Every device has to be approved. To be on my tailnet, a device has to be logged in with my account. Knowing the IP isn't enough. Having the password isn't enough. The device itself has to be authorised.
Identity, not just passwords. Login goes through an identity provider (like Google or GitHub), so it gets their two-factor authentication too.
Access rules (ACLs). You can define who can reach what. For example, my phone can reach the production apps, but maybe not the Proxmox admin panel. Even inside my own network, things don't have to be wide open.
Keys expire. Device keys expire by default and have to be re-authenticated, so a lost or old device doesn't stay trusted forever.
Compare that to the old way: an open SSH port on a public IP, protected by a password or a key, getting hit by thousands of login attempts a day from bots. Tailscale doesn't just make that safer. It makes the attack surface basically disappear.
Cloudflare Tunnel: putting apps on the internet
Tailscale is perfect for me. But my blog and my apps need to be reachable by everyone, and I'm not going to ask every visitor to install Tailscale.
That's where Cloudflare Tunnel comes in. It's for public access.
The idea is the same: connect out, not in. On the server, you run a small program from Cloudflare called cloudflared. It makes outbound connections to Cloudflare's network and keeps them open. When someone visits your domain, Cloudflare receives the request at its edge, and sends it down that already-open tunnel to your server.
Visitor's browser
|
v
Cloudflare edge (TLS, DDoS protection, caching)
|
| <== tunnel opened FROM my server, outbound ==>
v
cloudflared on my production VM
|
v
My ASP.NET Core app on localhost:5000
The visitor never connects to my home. They connect to Cloudflare. Cloudflare connects to my server only through the tunnel that my server opened itself.
What the setup looks like
After creating a tunnel and logging in, the configuration is a small YAML file. This is roughly what it looks like (with example names):
tunnel: <TUNNEL-ID>
credentials-file: /etc/cloudflared/<TUNNEL-ID>.json
ingress:
- hostname: app.example.com
service: http://localhost:5000
- hostname: api.example.com
service: http://localhost:5100
- service: http_status:404
Each hostname maps to a local service. The last line is a catch-all, so anything that doesn't match gets a 404 instead of reaching something it shouldn't.
In DNS, the domain just points to the tunnel instead of to an IP address. Cloudflare can set this up for you with one command:
cloudflared tunnel route dns my-tunnel app.example.com
And then cloudflared runs as a service on the VM, so it starts with the machine and reconnects on its own.
That's it. There's no IP address anywhere in this setup. My app is reachable at a normal domain with HTTPS, and my home IP never appears in DNS.
What you get from Cloudflare Tunnel
- No open ports. Same as Tailscale, nothing on my router is open.
- My home IP stays hidden. Visitors see Cloudflare's IPs, not mine.
- HTTPS is handled for me. Cloudflare gives the domain a certificate at the edge. No more fighting with certificate renewal.
- DDoS protection. Attacks hit Cloudflare's network, not my home connection.
- Extra protection if I want it. With Cloudflare Access, I can put a login screen in front of a hostname, so even a public URL (like a UAT site) only works for people I allow.
The honest trade-offs
I don't want to pretend it's perfect.
With Cloudflare Tunnel, Cloudflare terminates TLS at its edge. That means Cloudflare can technically see the traffic between the visitor and my app. For a personal blog and portfolio apps, I'm fine with that. For something very sensitive, it's worth thinking about.
You're also depending on Cloudflare. If they have an outage, my apps are down too. And they have rules about what you can serve through the tunnel, so it's not the right fit for something like streaming lots of video.
And of course, the homelab itself is still at home. If my power or internet goes down, my apps go down with it. Tailscale and Cloudflare can't fix that. A VPS in a data centre is still more reliable in that sense.
How I use them together
The way I think about it is simple:
| Tailscale | Cloudflare Tunnel | |
|---|---|---|
| Who it's for | Me (and devices I approve) | Everyone on the internet |
| What I use it for | Proxmox, SSH, testing VM, admin tools | Public websites and APIs |
| How you reach it | Private Tailscale name or 100.x IP |
A normal domain with HTTPS |
| Open ports needed | None | None |
| Static IP needed | No | No |
Admin things stay private on Tailscale. Public things go through Cloudflare. And nothing on my home router is open at all.
This also fits nicely with my separate VMs. Production runs cloudflared for the public apps. The testing VM is only on Tailscale, so UAT stays private, unless I specifically decide to share something.
What I learned
The biggest lesson for me was this: you don't need to expose your server to make it reachable. For years I thought hosting meant opening ports and owning a static IP. It doesn't. Outbound connections plus a bit of clever networking get you the same result, with much less risk.
I also learned a lot about networking along the way. NAT, WireGuard, DNS, TLS termination, reverse proxies. These were all words I knew from interviews, but setting up my own homelab made them real. When something didn't work, I had to understand why, and that's when it sticks.
And honestly, the feeling of opening my phone on a train, connecting to a server sitting in my room, with no ports open and no static IP, still makes me smile a little every time.
If you have a spare machine at home, even an old laptop, try it. Install Tailscale first, it takes five minutes. Then put one small app behind a Cloudflare Tunnel. You'll learn more from that weekend than from a lot of tutorials.
Are you running a homelab too? Do you use Tailscale, Cloudflare Tunnel, or something else like WireGuard directly or a reverse proxy on a VPS? Tell me in the comments. I'm always curious how other people solve this.
Comments (0)
🔑 Log in to comment Sign in with your email to like, save and join the discussion.
No comments yet. Be the first!