Home / Case studies / WireGuard Remote Access
Personal Project · Remote Access

Remote Access with WireGuard

I host a web server on my home network and needed dependable access to it while away, without exposing SSH or admin services directly to the internet. I set up a WireGuard VPN so the server is reachable over an encrypted tunnel from any network. The configuration was straightforward; most of the time went into tracking down a NAT routing fault and a single mistyped key.

Background

The server runs on my home network and, by default, is only reachable from there. I wanted access from outside the house without exposing SSH or the admin interface to the public internet, which rules out plain port forwarding.

A VPN solves this cleanly, and WireGuard is a lightweight, modern option with a small attack surface and simple key-based configuration. The requirement was narrow: reach the server from my laptop on any network, over an encrypted tunnel, with a single port open to the outside.

Outcome: the server is reachable over the tunnel from any network, the connection re-establishes automatically after a reboot, and only one UDP port is exposed to the internet.

Network layout

The laptop connects as a WireGuard client. The server runs the WireGuard interface and assigns addresses from a private 10.0.0.0/24 range: the server is 10.0.0.1 and the laptop is 10.0.0.2. Once the tunnel is established, the server is reachable at 10.0.0.1 as though it were on the local network, regardless of which network the client is on.

Clients reach the home network through a single encrypted tunnel rather than exposed ports. Click to zoom
Each client receives a 10.0.0.x address inside the tunnel. The server sits at 10.0.0.1, and outbound traffic is routed through it. Click to zoom

Implementation

01
Keys
Key generation was the first issue. The command pipes the generated private key into a root-owned directory, but sudo only applied to the first command in the pipe, so the write failed. Running the full pipeline inside a single elevated shell resolved it, after which I restricted the private key to owner-only permissions.
WireGuard Key pair Linux permissions
02
Server side
The server configuration in wg0.conf defines the interface address, the listening port, the private key, and a peer entry for the client. The PostUp and PostDown rules apply the iptables forwarding and masquerade rules that move tunnel traffic onto the network. wg-quick manages the interface, enabled as a service so the tunnel is restored automatically on boot.
wg0.conf wg-quick systemd iptables NAT
03
Client side
The macOS client uses a short configuration with the server's public key, its endpoint, and the address range routed through the tunnel. With the tunnel active, the client holds 10.0.0.2 and communicates with the server directly.
macOS client Endpoint AllowedIPs

Troubleshooting

The interface came up on the first attempt and the client reported an active tunnel, but no traffic passed. Pings to the server from outside the network timed out.

I worked through the obvious causes first. The firewall was inactive, the router was forwarding the correct port, and the server was listening. Packets were reaching the network interface but not entering the tunnel.

Two separate faults were involved. The masquerade rules referenced the wrong network interface, so return traffic had no path out; correcting the interface in the PostUp and PostDown rules fixed the routing. The second fault was a key mismatch. The client's public key stored on the server did not match the key derived from its private key, off by one character, since a capital I and a lowercase l are easy to confuse in many fonts. Deriving the public key with wg pubkey and comparing it against the peer configuration confirmed it. With both corrected, the connection established immediately.

Neither was a flaw in the design. Both were small configuration errors that were easy to introduce and slow to find, which is fairly typical of first-time VPN setups.

In use

Running wg show on the server reports the tunnel state, the connected peer, the last handshake, and throughput. To connect, I enable the tunnel on my laptop and SSH to 10.0.0.1 as though the server were on the local network.

Because the service starts with the machine, remote access survives a reboot without manual steps. It has been stable since, and it removed any need to expose management services publicly.