Homelab Rebuild Part 11: The Way In From Outside
What this is about Link to heading
After part 10 the segmentation stands. Every device sits in its network, and between the networks only what I allowed gets through. What’s missing is the way in from outside โ so that in someone else’s wifi I don’t have to rely on the operator knowing what they’re doing.
WireGuard is the obvious choice: a few lines of configuration, one UDP port, no certificate management. The interesting part isn’t the setup, it’s the question that comes before it.
A VPN client is just another device on a network. Which one?
That’s why this comes only now. Before parts 9 and 10 I’d have had no answer beyond “the main network, like everything else” โ and that is exactly what I set out to get rid of.
The question I got stuck on Link to heading
Three clients: my laptop, my phone, and an access for a family member. For two of them the answer came quickly โ both only need the way out to the internet, no access inward.
The laptop is where I got stuck. It feels like it belongs in my trusted VLAN; that’s where it lives at home, after all. And still it didn’t sit right.
The reason, which I could only phrase properly while writing this: the laptop in the living room and the laptop on hotel wifi are not the same device. Physically yes, in terms of trust no. Between those two states lie two weeks on foreign networks, an airport, and the possibility of it going missing. If the tunnel carries the same rights as the cable at home, then it is the shortcut past everything part 10 built.
There’s a technical argument on top: a VPN client gets an address from the tunnel, but no VLAN tag. Actually placing it in an existing VLAN would need bridging. Pretending via rules is possible โ but then you maintain two rule sets that will certainly drift apart.
So the tunnel gets its own network, 10.0.60.0/24, deliberately outside the VLAN
numbering, because it isn’t a VLAN on the switch. And rights aren’t granted per
network but per tunnel address:
| Client | Address | Access inward |
|---|---|---|
| Phone | 10.0.60.4 |
none |
| Family member | 10.0.60.2 |
none |
| Laptop | 10.0.60.3 |
firewall, virtualisation host, access point |
That works because the tunnel address is bound to a key. Unlike a MAC it can’t simply be claimed โ and that is exactly what you have to get right during setup, more on which below.
The endpoint: an address that changes every couple of years Link to heading
My connection has a fixed address, but only semi-fixed โ every one or two years it changes. Rare enough to forget, frequent enough that all client profiles eventually point nowhere.
I already had a solution for this, and it was a good example of how homemade fixes
age. A script on the router called a URL on my web server with its own address as
a parameter. A second script fished it out of the Apache log with grep and wrote
it into a text file.
Two problems with that, and only one of them was on my radar:
It hadn’t worked at all since the rebuild. The script read the address off
sfp-wan on the access point. Since that box went from router back to switch,
that port is the trunk to the firewall and carries no public address. The script
kept running, it just found nothing.
And a text file doesn’t help WireGuard. The client holds an Endpoint, which
is resolved when the connection is established. What you need is an A record that
changes โ then clients re-resolve by themselves after a failed handshake. A file
containing the right address has to be read by a human and copied into three
profiles.
The replacement was unspectacular: the os-ddclient plugin on the firewall, an
API token at the DNS provider, a subdomain record with a short TTL. The firewall
knows its own public address, so the whole detour via logfile and third-party
server falls away.
The lesson: the homemade fix wasn’t the problem, the problem was that it broke silently. A script that finds nothing and still exits with status 0 never reports in.
One field, two meanings Link to heading
Before the mistakes, the one concept you have to have understood.
The most common stumbling block in WireGuard, and it goes by the same name in both configurations.
On the firewall, AllowedIPs is the binding of key to address. With
10.0.60.4/32 in there, WireGuard accepts from that key only packets carrying
exactly that source address. Enter a whole network generously and one peer could
impersonate another โ and every rule resting on a single tunnel IP becomes
worthless.
In the client profile the same field means something entirely different, namely
which traffic the client sends through the tunnel. 0.0.0.0/0 means
everything. That’s the full tunnel, and it’s what I want here, because the whole
point is not having to trust someone else’s wifi.
You can configure both sides correctly without knowing the difference. Right up until you start building per-client rules โ at which point their effectiveness hangs on the server side of that field.
What went wrong Link to heading
Four things, and three of them had the same symptom.
The switch that exists twice Link to heading
Instance created, three peers created, peers assigned to the instance, saved,
applied. In the status view: nothing. wg show produced no output, the interface
didn’t exist at all.
The reason was at the bottom of the same page: a global Enable WireGuard, independent of whether the instance itself is enabled. Two checkboxes with the same meaning on two levels, and I had ticked the upper one.
The interface that isn’t one Link to heading
Even after enabling it you can’t build a firewall rule. The tunnel exists, but not as an interface in the firewall’s sense โ for that it has to be assigned under Interfaces โ Assignments. Only then does a tab appear under Firewall โ Rules.
Two traps while assigning: the IPv4 configuration stays on None, because the tunnel address is managed by WireGuard itself โ enter it again here and you have two interfaces in the same subnet. And block private networks must stay off, because the tunnel carries nothing but private addresses.
The key I didn’t carry over Link to heading
The most interesting mistake, because it disguised itself as a network problem.
Phone connected over mobile data, DNS name resolved correctly, port was right. On the client: a few kilobytes sent, zero received. On the firewall, all three peers: zero received.
The capture on the WAN showed the packets arriving. The hit counter on the WAN
rule was counting. And wg show stayed at zero.
The cause: I had carried over the clients’ public keys from the old configuration โ those were fine. What I hadn’t carried over was the server’s public key. It was generated fresh when the instance was created, while the client profiles still held the one from the old router.
So the phone was encrypting its handshake for a recipient that doesn’t exist. WireGuard couldn’t decrypt the packet and dropped it without comment โ no log, no counter, no error.
The rule in the wrong place Link to heading
And finally the mistake from part 10 in new clothing. I wanted to watch TV over the tunnel too; for that I run a small service that translates the IPTV multicast into an ordinary HTTP stream.
The rule for it existed and access was blocked anyway. It sat below the block rule against private networks โ and the target is a private address. Moved up, and it worked.
At least I saw it immediately, because the block rule logs. That’s the checkbox I set in part 10, paying off here for the first time.
The rule of thumb Link to heading
WireGuard stays silent for every failure alike. Wrong key, wrong endpoint, blocked packet, service not started โ the symptom is always that nothing happens. There is no “connection refused” and no error in the log.
That’s a design decision in the protocol, not sloppiness: WireGuard never replies to packets it can’t attribute. From outside the port looks closed, and a scanner learns nothing about what’s listening there.
For troubleshooting that means guessing doesn’t work, because every cause looks the same. You check from the outside in and see where the chain breaks.
Four steps, four possible causes. Mine broke between 2 and 3 โ the firewall was counting hits, WireGuard wasn’t.
That combination was exactly the key mistake: the packet filter sees the packet and lets it through, the service can’t decrypt it. Anyone working from “it doesn’t work” alone would probably keep fiddling with firewall rules at that point.
Where I stand now Link to heading
The tunnel runs, tested on the phone over mobile data. Full tunnel, DNS via my own firewall, IPTV working. The phone and the family member’s access reach the internet and nothing internal โ the same chain of four as in part 10, with one rule for a single host inserted in the middle.
From outside, exactly one UDP port is open, and it only answers packets carrying a valid signature.
Two things stay open:
The laptop. It gets set up tomorrow. Its rule currently sits wide, but with logging โ the same method as the receiver in part 10: do everything you’d typically do while travelling, then read the real targets and ports off the log and narrow it down. That’s more honest than guessing now which ports the management interfaces actually need.
The emergency access stays out. Besides its regular address, my access point has a recovery interface in its own subnet with no connection to the rest โ the way out after two lockouts back in part 3. It isn’t reachable over the tunnel, and it should stay that way. Which does mean: if I lock myself out, the VPN won’t help. That still takes a cable and being there in person.
And one side note for the record: my IPTV channel list still contains addresses from before the rebuild. Over the tunnel each of those entries runs into a timeout before the player tries the next one. Cleaning that up is pending.
Next step Link to heading
With the tunnel, the network side is together for now. What comes next is a different layer: identity. Right now every service has its own user management, and that scales about as well as a flat network.
Part of a series about rebuilding my home network. Status: September 2026. Questions, corrections and your own experiences are welcome in the comments.