← Learn··Updated 31 May 2026·10 min read

Why self-host behind a VPS

Home networks are not built to face the internet — NAT, CGNAT, and dynamic IPs all get in the way, and port-forwarding puts your home IP in public DNS. This post explains why, and compares the realistic ways to expose a homelab safely before the series settles on a VPS plus WireGuard tunnel.

Networking
Expose your homelab to the internetPart 1 of 7
#networking
#vps
#wireguard
#self-hosting
#ai-assisted

level:Intermediate

This is Part 1 of Expose your homelab to the internet. It is the why post: no commands, no config files, just the reasoning. Before you provision a server or touch DNS, it helps to understand exactly what is wrong with a home network as an internet-facing thing, why the obvious fix (port-forwarding) is the wrong one, and what the realistic alternatives trade against each other. By the end you should understand why the rest of this series builds a public VPS with a WireGuard tunnel rather than any of the easier-looking options.

The problem: home networks are not built to face the internet

What. A home network is designed for one direction of traffic: devices inside reach out to services on the internet. The reverse — something on the internet reaching in to a service running on your home server — is the thing every home self-hoster eventually wants, and the thing your network actively prevents. Three separate mechanisms get in the way, and it is worth pulling them apart because they fail in different ways.

NAT: inbound connections have nowhere to go

What. Your home router does Network Address Translation. Your ISP gives you (typically) one public IP address, and every device behind the router — laptop, phone, server, smart bulb — shares it. The router keeps a table mapping internal addresses to that single public address.

Why it blocks you. That table is built from outgoing connections. When your laptop opens a connection to a website, the router records "this internal device is talking to that server" and knows where to send the replies. But an unsolicited packet arriving from the internet has no matching table entry — the router has no idea which of your dozen internal devices it is meant for, so it drops it. From the outside, your home server is invisible. There is no address that points at it.

How people usually work around it. Port-forwarding: you manually tell the router "anything arriving on port 443, send to 192.168.1.50." That punches a permanent hole through NAT. It works — and it is exactly the approach this series argues against, for reasons we get to below.

CGNAT: you may not even have a public IP

What. Carrier-grade NAT (CGNAT) is NAT run a second time, by your ISP, across many customers. Instead of giving each household a real public IP, the ISP puts hundreds or thousands of customers behind a shared pool of public addresses. This is increasingly common on mobile, fibre, and budget broadband because IPv4 addresses are scarce and expensive.

Why it blocks you. Under CGNAT the "public" IP your router sees is itself private — it belongs to the ISP's internal network, not the internet. You cannot port-forward to an address you do not control. Even if you configured your router perfectly, the ISP's CGNAT layer would drop the inbound packet exactly the way your own NAT does. Port-forwarding is simply not available to you.

How to tell. If the IP your router reports on its WAN interface does not match the IP a site like a "what is my IP" service shows, you are almost certainly behind CGNAT. Addresses in the 100.64.0.0/10 range are a strong tell.

ℹ️ Note — CGNAT is not a misconfiguration you can fix. It is your ISP's architecture. Some ISPs will hand you a real public IP on request (sometimes for a fee); many will not. The VPS approach in this series sidesteps the question entirely, because it never relies on an inbound connection to your home at all.

Dynamic IPs: the address moves

What. Most residential connections get a dynamic public IP — the ISP can reassign it whenever your router reconnects, on a lease that might last hours or months.

Why it blocks you. Even if you have a real public IP and can port-forward, a DNS record pointing app.example.dev at today's address goes stale the moment the ISP rotates it. Your service silently disappears until you update DNS. The usual patch is dynamic DNS — a small client that detects the change and updates the record automatically — which leads directly into the next problem.

Why not just port-forward and use dynamic DNS

What. The classic home-self-hosting recipe is: open ports 80 and 443 on the router, forward them to your server, and run a dynamic-DNS client so a hostname always points at your current home IP. It is well-documented, costs nothing, and works on a connection that has a real public IP.

Why it is the wrong default. It solves reachability by sacrificing two things you should not give away.

First, it publishes your home IP address in public DNS. Anyone who resolves your hostname now has the IP of the line your family lives behind. A public IP plus geolocation databases plus your ISP's published ranges narrows your physical location and identifies your provider. That IP is also now a fixed target: it can be scanned continuously, probed for known vulnerabilities, and — if anyone takes a dislike to you — hit with a DDoS that saturates your home connection and takes down your internet for everyone in the house, not just the service. You cannot easily change a home IP, and you cannot hide behind anything because the record points straight at you.

Second, every open port is attack surface on the network where your personal devices live. Port-forwarding does not expose "a service" in isolation — it exposes a path from the open internet into your home LAN. The same network segment holds your laptop, your phone, your NAS, your backups, your smart-home gear. If the forwarded service has a vulnerability (and self-hosted software gets CVEs constantly), the foothold an attacker gains is inside your home, one hop from everything else. The blast radius of a compromise is your whole life, not a disposable box.

⚠️ Warning — Opening a port on your home router is not like opening a port on a cloud server. A cloud server is a sealed, single-purpose machine you can wipe and rebuild. Your home LAN is where your personal devices live. Treat an inbound hole into it as a serious decision, not a convenience toggle.
Danger — Putting your home IP in a public DNS record is effectively publishing your home address and ISP to anyone curious enough to look. For a service you intend to share with strangers, or even just expose to the open internet, this is a privacy and availability risk you do not get back once it is indexed.

There are narrow cases where port-forwarding is defensible — a single hardened service, a static business IP, a DMZ'd box with nothing else on its segment. But as a default for a home line, it trades away privacy and isolation for convenience, and the trade is rarely worth it.

The pattern this series uses

What. Stop trying to make your home network face the internet at all. Instead, rent a cheap public VPS (virtual private server) and let it be the part of the system that touches the internet. The VPS holds the public edge: it owns the domain, it has the open ports, it terminates TLS. A private WireGuard tunnel reaches from the VPS back into your homelab. Your home network stays completely private — zero open ports, home IP never in DNS.

Why it solves all three problems at once. The connection that builds the tunnel is outbound from your home to the VPS. Outbound is exactly the direction NAT and CGNAT allow — the same direction as browsing a website — so neither one blocks it. Because your home only ever dials out, your dynamic home IP never needs to be known by anyone; the tunnel re-establishes itself wherever your home lands. And the only address that ever appears in public DNS is the VPS's fixed public IP, which is a disposable, single-purpose machine you can rebuild from scratch in minutes. The internet can see one hardened VPS and nothing else.

How it fits together (the concrete setup this series builds). Using the shared example throughout the series: the public host is app.example.dev, the VPS is a Hetzner Cloud CX22 running Debian 12 at public IP 203.0.113.10. A WireGuard interface wg0 carries a private subnet 10.8.0.0/24, with the VPS at 10.8.0.1 and the homelab at 10.8.0.2, over UDP port 51820. Caddy on the VPS terminates HTTPS and reverse-proxies each request down the tunnel to the right internal service. Your home router needs no port forwards at all.

flowchart LR
    V["Visitor<br/><i>browser</i>"] -->|"app.example.dev"| D["Public DNS<br/><b>A record →</b><br/>203.0.113.10"]
    D --> E["VPS<br/><i>Hetzner CX22 · Debian 12</i><br/>Caddy terminates TLS"]
    E -.->|"WireGuard tunnel<br/>UDP 51820 · wg0"| H["Homelab service<br/><i>10.8.0.2</i><br/><b>no open ports</b>"]

The end-state topology: a visitor resolves the domain to the VPS's public IP, the VPS terminates TLS, and the request travels down the encrypted WireGuard tunnel to a homelab service that has no open ports of its own. The home IP appears nowhere in this picture.

💡 Tip — Notice the arrow into the homelab is dashed and points out from the home: the tunnel is established by the homelab dialing the VPS. That single design choice is what makes NAT, CGNAT, and dynamic IPs irrelevant. The home is always the client; the VPS is always the server.

The realistic approaches, compared

The VPS-plus-WireGuard pattern is one of several legitimate ways to expose a homelab safely. They differ in how much you operate yourself, how much you depend on a third party, and how much you learn. Here is the honest comparison.

Approach Cost Vendor lock-in Operate yourself Where traffic flows Best for
Raw WireGuard + self-run VPS (this series) ~€4/mo VPS None Yes — you run the VPS Visitor → your VPS → your tunnel Full control, learning the fundamentals
Cloudflare Tunnel (cloudflared) Free tier High (Cloudflare) No VPS needed Visitor → Cloudflare → your daemon Fast setup, no server to run
Tailscale Funnel Free tier High (tailnet) Minimal Visitor → Tailscale infra → your node Easiest, if you already use Tailscale
frp / rathole (self-hosted) ~€4/mo VPS None Yes — you run both ends Visitor → your VPS → your reverse tunnel A lighter tunnel than WireGuard

Raw WireGuard tunnel to a self-run VPS — this series

What. You rent a small VPS, run WireGuard yourself to build the tunnel, and run Caddy yourself to handle the public edge. No third party sits in the request path.

Why it is the recommended default here. You get full control and zero vendor lock-in — the only outside party is whoever rents you the VPS, and any provider with a Linux box and a public IP will do. Your traffic flows only through machines you operate. Most importantly for a learning series, it teaches the fundamentals: what NAT actually does, how a VPN tunnel is built, how TLS termination and reverse proxying work, how DNS resolves to an edge. Those concepts transfer everywhere. The cost is real but small — around €4/month — and the day-two operational work (updates, monitoring, certificate renewal) is yours.

How it compares. It is the most work of the four and the most rewarding to understand. If your goal is to learn how the internet-facing edge of a self-hosted setup actually works, this is the path.

Cloudflare Tunnel (cloudflared)

What. You run Cloudflare's cloudflared daemon on your home server. It dials out to Cloudflare's network and creates a tunnel; Cloudflare publishes the public hostname and proxies traffic down the tunnel to you. No VPS, no open ports, and it is free for typical use.

Why you might pick it. It is genuinely easy and removes the server you would otherwise operate. Cloudflare also gives you their CDN, DDoS protection, and TLS for free.

Why this series does not. Your traffic is proxied through Cloudflare — they sit in the request path and can see it (TLS is terminated on their edge). You are tied to one vendor's network and dashboard. And the free plan's terms of service restrict heavy non-HTML use: serving large amounts of video, audio, or other non-web content through the free proxy can violate the TOS and get you throttled or cut off. For a learning project about controlling your own edge, handing the edge to a vendor defeats the point.

Tailscale Funnel

What. Tailscale builds a private mesh network (a "tailnet") between your devices over WireGuard, with automatic key exchange and MagicDNS. Funnel is the feature that exposes a node from your tailnet to the public internet, routed via Tailscale's infrastructure.

Why you might pick it. It is the lowest-effort option of all, especially if you already run Tailscale for private access. It is built on WireGuard under the hood, so the cryptography is sound, and MagicDNS makes naming painless.

Why this series does not. You are tied to your tailnet and Tailscale's coordination/relay infrastructure, traffic routes through their systems, and Funnel is limited to certain ports and use cases. It hides exactly the mechanics this series is trying to teach. Excellent tool; wrong tool for a from-scratch understanding.

frp / rathole — self-hosted reverse-tunnel daemons

What. frp and rathole are self-hosted reverse-tunnel daemons. You run a server component on a public VPS and a client component on your home server; the client dials out and the server forwards public traffic down the tunnel. They are an alternative to WireGuard for the tunnel itself.

Why you might pick it. They are purpose-built for exactly this — exposing specific ports/services through a relay — and are lighter than standing up a full VPN interface. rathole in particular is small and fast. No third-party SaaS; you own both ends.

Why this series does not. They are narrower than WireGuard. WireGuard gives you a general-purpose private network between the VPS and the homelab — once it exists, anything can route over it, not just the one tunneled service. That generality makes WireGuard the better thing to learn first, and the better foundation for the k3s routing the series ends on. frp and rathole are worth knowing as a lighter alternative once you understand the tunnel concept.

ℹ️ Note — None of these four is "wrong." They sit on a spectrum from most-managed (Tailscale, Cloudflare) to most-self-operated (WireGuard, frp/rathole). This series deliberately picks the self-operated end because the goal is to understand the edge, not just to make a URL work. If you only want the URL, Cloudflare Tunnel will get you there this afternoon.

Where this leads

You now have the reasoning: home networks block inbound traffic by design (NAT), often have no public IP to forward to at all (CGNAT), and move their address around (dynamic IPs). Port-forwarding "solves" this by publishing your home IP and opening your personal LAN to the internet — a bad trade. The fix is to put the public edge on a disposable VPS and reach back into the homelab over an outbound WireGuard tunnel, so the home stays fully private with zero open ports.

The rest of the series builds exactly that, from the outside in, using raw WireGuard and a self-run VPS for control, no vendor lock-in, and the fundamentals it teaches along the way.

Next: Buy a domain and point DNS gets you a real name and the records that point it at your VPS — the first concrete brick of the public edge. From there the series provisions the VPS, builds the tunnel, and puts Caddy in front of your first service. The full reading order lives on the hub: Expose your homelab to the internet.