Infrastructure
Stop Poking Holes in Your Firewall to Reach Your Own Stuff
Reaching a self-hosted service from anywhere usually doesn't require an inbound port at all — an outbound tunnel plus authentication is a smaller attack surface than a port-forward, and a name that resolves still isn't a service you can reach.
- Infrastructure
- Networking
- Security
- Homelab
I self-host a handful of small web things in my homelab, and eventually I want to reach one of them from outside the house. The reflex move is to forward a port on the router and call it done. I’ve stopped doing that, because there’s almost always a way to reach your own service that doesn’t involve standing up an inbound hole and then owning it forever. The instinct to open a port is usually a sign you haven’t looked at the better options.
There are basically three ways to make an internal service reachable, and they trade off convenience against exposure in a way worth understanding before you pick one.
Option one: just point DNS at it
The simplest path is a plain DNS record aimed straight at your origin — an A record
for IPv4 or an AAAA record for IPv6 — and the client connects directly. The
appealing part: it works on any port, because nothing is in the data path but DNS.
The cost is everything DNS doesn’t do for you. The real address of your box is now public. There’s no TLS termination you didn’t set up yourself, no protection against someone hammering the origin, and a sharp edge specific to IPv6: if your origin is v6-only, a client on a v4-only network can resolve the name and then fail to connect, because it has no route to a v6 address. That failure looks like a broken service, not a network mismatch, which makes it miserable to debug from the other end.
This option is fine for a convenience name on a network you control. It’s fragile the moment you mean “reach it from anywhere.”
Option two: hide behind a reverse proxy at the edge
Put an edge proxy in front (the “orange cloud” mode of providers like Cloudflare is the common example). Clients connect to the edge — over v4 or v6, which quietly solves the client-can’t-do-IPv6 problem — and the edge connects back to your origin. You get HTTPS, a hidden origin address, and some shielding against abuse.
Two catches that bite in practice:
- Edge proxies only listen on a fixed set of HTTP/HTTPS ports. If your service is on some arbitrary high port, the proxy won’t front it directly. You either move the service to a supported port or use an origin-port-override rule to map the public standard port to your odd internal one. Worth knowing before you spend an hour wondering why the proxy refuses your port.
- The origin still has to be reachable from the edge. The proxy connects to your box, so your box needs to be reachable from the public internet — not just from inside your own network. Which leads straight to the most useful distinction in this whole topic.
Routable is not reachable, and resolvable is not either
A name that resolves to an address you can’t route to is more confusing than no name at all. It looks solved right up until the connection hangs.
A global-looking IPv6 address that only actually routes inside your lab won’t work from the outside world for either of the first two options, no matter how perfectly the DNS resolves. “I gave it a record” and “the packet can get there” are independent facts, and conflating them is a classic time-sink — the same trap I hit going IPv6-only, where DNS was the first thing to break. Before you trust any name end to end, confirm something can actually open a connection to the service, not just look it up.
Option three: let the box dial out
This is the one I reach for most now. Run a small tunnel daemon on (or near) the host; it makes an outbound connection to a provider, and the provider routes your hostname through that tunnel back to the service.
What you get is the good part: no inbound port, no port-forward, no firewall hole. It works even if the host is behind NAT or only reachable internally, because nothing external ever connects to you — your box initiated the connection going out. That single inversion is a real security win:
- An inbound port is a thing you expose and then have to defend indefinitely. An outbound connection you initiate is a far smaller attack surface — there’s nothing sitting open and listening for the whole internet to find.
- It sidesteps the “is my address publicly routable?” question entirely, because reachability rides on the tunnel rather than on your network topology.
- It’s the natural place to bolt on authentication — an access layer in front of the tunnel — which matters more than people admit, for the next reason.
It’s the same instinct as deploying with the access you already have: the path that needs the least special exposure is often both easier and more robust than the obvious one.
A nice hostname makes an unauthenticated service easier to attack
Here’s the part it’s tempting to skip. The day you put a friendly, memorable hostname in front of a service, you’ve also made it dramatically easier to find. If that service has no authentication, you didn’t just add convenience — you advertised an open door. Publishing a name is, functionally, making the thing public.
So the rule I hold: put authentication in front before you make a service broadly reachable, not after. An outbound tunnel with an access layer gets you remote access and a login gate in one move, which is exactly why it’s my default. If a service is genuinely fine for anyone to read, that should be a decision you made, not a side effect of giving it a tidy URL.
Pick by your actual constraint
- Just want a memorable name, on a network with the right IP support, box already reachable → a direct DNS record. Cheapest, least robust.
- Want HTTPS and a hidden origin, and the box is publicly reachable → an edge reverse proxy, with a port-override rule if your service is on an odd port.
- Don’t want to expose anything inbound, want it usable from any network, and want a login in front → an outbound tunnel with an access layer. Most robust, and my default for anything leaving the house.
The throughline is simple: opening a port should be the option you reach for last, not first. Prefer the design that exposes the least, then add auth before you add reach. If you run your own services and have a remote-access setup you like, tell me what you landed on — and the private cloud notes cover more of how I think about this.