Just a day ago I launched this blog, with a first post about how my idea to self-host Immich turned into an entire homelab stack. But apparently I didn’t complicate it enough yet…
Soon after publishing the post, I got a message from a friend. He complimented my blog post, but also sent a screenshot of how a simple ping command now exposed my home IP, which could potentially reveal an approximate location of the server.
I had thought about this risk initially while setting everything up. However, I didn’t see it as too big of a problem at first, as I was just hosting a personal Immich instance for myself and maybe some people I know, so no one would accidentally stumble across it.
A public blog changed that - After all, random strangers might now access a service hosted on my server, and I wouldn’t want my residential IP to be publicly discoverable to any of them. So, of course, I decided to overengineer a solution.
How To Hide Your Homelab IP
This issue can be solved in a few ways. The most straightforward way would have been to delegate the hosting of any publicly accessible services to a VPS. So the DNS would simply point there, and it would expose the public IP of the VPS instead of my home IP.
However, I didn’t want to limit my homelab to just private projects. I wanted to have the flexibility to decide where each new service should live - my homelab or a VPS, regardless of whether it would be a public or a private service.
I found cloudflare to be a commonly used solution for this. Cloudflare can act as a reverse proxy for HTTPS traffic. Instead of someone looking up duckylu.uk and getting the server’s IP, they would get a Cloudflare IP address. Cloudflare receives the request and forwards it to the server.
This has a few nice advantages. Cloudflare handles things like TLS certificates and offers DDoS protection with relatively little to set up and configure. It’s a convenient solution for a homelab - but convenience doesn’t really seem to be my thing.
Using Your Own Reverse Proxy
Instead, I looked for an option that could cut out Cloudflare as a dependency. And a good option turned out to be to “simply” create my own reverse proxy. I could host this on a small VPS, where the DNS records point to the VPS’s public IP, which then forwards it to the homeserver.
I already had a small VPS running with a few services. Conveniently, it was also already running Caddy as a reverse proxy to expose these services. So ultimately I just needed to modify the Caddyfile to forward requests for my blog domain to the homeserver IP:
duckylu.uk {
reverse_proxy https://<MY_PUBLIC_IP> {
header_up Host duckylu.uk
transport http {
tls_server_name duckylu.uk
}
}
}
The Host header is important here, as the Caddy configuration on the homeserver uses the domain to decide which service should handle the request. Adding the header makes sure that this information is preserved between Caddy on the VPS and Caddy on my homeserver.
Then I just needed to update my DNS record to point to my VPS IP instead of my home IP, and everything worked as expected - duckylu.uk now resolved to the VPS’s public IP.
Making It Slightly More Secure
While my current solution did its job, I decided that I wanted to flesh it out a bit more. Currently, my router still had to forward port 80 and 443 to my homeserver so the Caddy proxy on the VPS could reach it. Finding a way to close these ports while still allowing the VPS to direct to them would make my network inaccessible from the public internet, except through the VPS.
I decided to use Tailscale for this. Tailscale can be used to create a private network between the homeserver and the VPS, allowing them to communicate securely without directly exposing them to the internet.
Tailscale is built on WireGuard. For this use case, Tailscale does much more than we need and WireGuard itself would have been more than enough. However, as I was already using Tailscale for private remote access to my homelab, Tailscale didn’t really add much complexity.
Tailscale provides their own control service which you can use to orchestrate the networks and devices. To avoid introducing this as another dependency, I have been hosting Headscale on my VPS as an open-source alternative for the control server.
I added the VPS to my existing Headscale network, so it could communicate privately with the homeserver (which was already on the network), without requiring anything to be publicly exposed on my home network.
To avoid giving the VPS more access than it needs, I configured an additional grant in the Headscale configuration, next to the existing grants for letting my devices access the homelab and VPS. This grant makes sure that the VPS can only connect to the homeserver on TCP port 80 and 443.
{
"grants": [
{
"src": ["web-proxy@"],
"dst": ["homelab-pi@"],
"ip": ["tcp:80", "tcp:443"]
}
]
}
Then I just modified the Caddy configuration on the VPS to point to the internal Tailscale IP instead of the homeserver’s IP:
duckylu.uk {
reverse_proxy https://<INTERNAL_TAILSCALE_IP> {
header_up Host duckylu.uk
transport http {
tls_server_name duckylu.uk
}
}
}
After this, I could close the forwarded ports on my router. The homeserver was no longer publicly reachable, while the domain continued to work normally through the VPS.
Every Solution Has Its Downsides
This extra bit of setup helped me to hide my home IP address while still running this blog (and other services) from the server running inside my home.
This is what a request looked like before:
And here’s what it looks like now:
Unfortunately, my current setup still has one major downside: There are more potential points of failure. The VPS needs to be running for services on my homeserver to be reachable, and both its Caddy and the Tailscale connection need to be working, on top of all the potential things that could break initially already. Now something could fail on the VPS, causing the services to become unreachable even if the homeserver itself is perfectly healthy. The VPS is probably less likely to fail than my homeserver, but it’s still worth acknowledging the additional risk. For me, that trade-off is worth it. I can keep my home network private while still exposing services from home.