Wireguard to access a home network
I work remotely from home, and over the years I’ve amassed a bunch of machines related to that (development, testing, benchmarking, …), and other devices you may usually find at home (printer, NAS, …). Occasionally I need remote access, and for a while SSH tunnels were good enough. I decided to simplify and clean this up and use a proper VPN wireguard. This blog post explains the setup I used.
None of this is somehow novel or invented by me. I had an idea of what I wanted to achieve, but I’m not a networking guy. Start talking about tunnels, forwarding, NAT, masquerade, … and I get confused quick. If you’re doing this for a living it’s probably obvious, and I’m sure it’s somewhere deep in the wireguard docs. But it took me a while to make this work the way I wanted.
So here’s how I did it, maybe it will help someone.
Of course, I started by searching the web, looking for how others did this. But I found nothing very useful. The results are either ancient (before wireguard), discuss one tiny piece (as you do on stackoverflow). Or it’s a dubious response generated by AI, with various inaccuracies and no way to correct them. If this is the future, I don’t like it …
Here’s a diagram of what I wanted to achieve: I have a home network on 192.168.0.1/24, behind a router, and I want to make this accessible from the outside. A machine with internet access (labeled as “client”) should be able to access IPs on the home network.
A straightforward way is to get a public IP for the router, and forward a port to some kind of gateway (SSH, VPN, …) inside. I chose not to do this, because I absolutely hate the ISP-provided modem. The less I have to interact with it, the better for my mental health. And I have other reasons for the external gateway anyway, so I chose to use it.
I’m going to use two private subnets (the exact nets don’t matter):
- 192.168.1.0/24 - home network (regular ethernet)
- 172.31.0.0/24 - wireguard subnet (VPN)
The goal is to allow the client to access addresses on the internal network, e.g. by doing
$ ping 192.168.1.1
The idea is that we set up three wireguard hosts, forwarding traffic to the internal network:
external gateway (173.31.0.1) - This is the wireguard “server” on the public internet. Can be a small VPS somewhere. Clients will connect to it, and it’ll forward traffic to the internal one.
internal gateway (172.31.0.2) - This is the wireguard “server” on the home network. I’m running this on an old Raspberry Pi 4, which is more than enough. It forwards traffic from the external gateway to the actual internal hosts.
client (172.31.0.3) - A client on the public internet (e.g. my laptop while I travel) connects to the external gateway, and routes all traffic to internal IPs through it.
Wireguard has nothing like “server” or “client” (or “gateway”), everything is a “peer.” I use server/gateway for hosts accepting connections from many other hosts, and client for hosts that connect to a single host at a time.
Installing wireguard
Before we start setting up the peers, you need to install wireguard packages, and related tools (e.g. wg-quick). I’m using Debian, so this does the trick for me:
# apt install wireguard
You may need to use a different command depending on the system, see the wireguard docs.
Setting up peers
We’ll set up the hosts in the order listed above, i.e. external gateway, internal gateway and client. We’ll need to set up the wireguard peers, routing and IP forwarding.
To setup the tunnels we’ll need three sets of key pairs, so let’s generate them:
for k in 1 2 3; do
wg genkey | tee privatekey | wg pubkey > publickey
echo "KEY $k" $(cat privatekey) $(cat publickey)
done
which on my machine generated this:
KEY 1 kC+mnBYjGXL/Gm/6NGb4p6o60u0jzbo91Ou+VudaiFw= zhyz6Z3H2eblr5xVXsaaQlsEQjwnvOXSRDWEKkeHmD4=
KEY 2 8HpdppOcIv/hc4fFzx+NW9JZ8vAwxU2ZVKI7xg4Hdk0= ccSDfbZ4dAzR8TnRKc67nCqvRQoCdmpTsaP6mHlFdQo=
KEY 3 GJtyR3aqpfh4zwUJvfBStLEcWHxb9ymng+Aq2SbfW2o= Ns2YZ9hj9ahoecR9BU73gDRPHI1Kacr3iB4AqN1bWzU=
We’ll use these keys in the following sections.
External gateway
This is the “main” server, accepting tunnels opened by either the
internal gateway or clients. First, configure the wg0 interface.
Create file /etc/wireguard/wg0.conf with this content:
# external gateway
[Interface]
Address = 172.31.0.1/32
ListenPort = 1194
PrivateKey = kC+mnBYjGXL/Gm/6NGb4p6o60u0jzbo91Ou+VudaiFw=
# internal gateway
[Peer]
PublicKey = ccSDfbZ4dAzR8TnRKc67nCqvRQoCdmpTsaP6mHlFdQo=
AllowedIPs = 172.31.0.2/32, 192.168.1.0/24
# client
[Peer]
PublicKey = Ns2YZ9hj9ahoecR9BU73gDRPHI1Kacr3iB4AqN1bWzU=
AllowedIPs = 172.31.0.3/32
And then define / enable the wg0 interface in systemd, so that it’s
started automatically.
# systemctl enable wg-quick@wg0
# systemctl start wg-quick@wg0
This is a minimal config. You may customize this, if needed.
The important bit is the AllowedIPs for the internal gateway. This
tells wireguard where to route traffic for the home network (received
e.g. from the client peer). Wireguard will do this automatically, you
don’t need to set any additional routes.
You need to enable IP forwarding, though. The easiest way to do that is from a command line:
$ sudo sysctl -w net.ipv4.ip_forward=1
You probably want to persist that, so that it happens after every boot.
Create file /etc/sysctl.d/enable-ip-forward.conf with this content
net.ipv4.ip_forward = 1
Internal gateway
The internal gateway is next. Create the wg0 interface, with this
definition (189.170.14.87 is the public IP of the external gateway,
assigned to the VPS/VM)
[Interface]
Address = 172.31.0.2/24
ListenPort = 1194
PrivateKey = 8HpdppOcIv/hc4fFzx+NW9JZ8vAwxU2ZVKI7xg4Hdk0=
# public gateway
[Peer]
PublicKey = zhyz6Z3H2eblr5xVXsaaQlsEQjwnvOXSRDWEKkeHmD4=
Endpoint = 189.170.14.87:1194
AllowedIPs = 172.31.0.1/24
Then enable the wg0 interface using systemctl, just like for the
external gateway. The host needs IP forwarding too, so enable that too
(through sysctl).
There’s one more thing this needs to do - NAT between the two networks. This translates IP addresses, so that hosts in the home network see the traffic as coming from 192.168.1.0/24 (and not from the wireguard subnet).
With nftables, you can do this by adding this to /etc/nftables.conf
and restarting the nftables service:
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 172.31.0.0/24 oif "eth0" masquerade
}
}
You may need to adjust the interface name, connected to the home
network. In my case it’s eth0.
Client
Finally, let’s configure the client. This host needs only the wg0
part, nothing else (without the forwarding and NAT). Define the wg0
like this:
# client
[Interface]
Address = 172.31.0.3/24
ListenPort = 1194
PrivateKey = GJtyR3aqpfh4zwUJvfBStLEcWHxb9ymng+Aq2SbfW2o=
# external gateway
[Peer]
PublicKey = zhyz6Z3H2eblr5xVXsaaQlsEQjwnvOXSRDWEKkeHmD4=
Endpoint = 189.170.14.87:1194
AllowedIPs = 172.31.0.1/24, 192.168.1.0/24
And enable/start the wg-quick@wg0 interface using systemctl.
Notice the AllowedIPs, which tells wireguard to route 192.168.1.0/24
through the wireguard tunnel.
Summary
At this point you should be able to ping the home network from the wireguard client:
$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=61 time=44.1 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=61 time=44.6 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=61 time=42.6 ms
64 bytes from 192.168.1.1: icmp_seq=4 ttl=61 time=41.5 ms
We had to do these steps:
- external gateway
- setup
wg0on interface (with peers for internal gateway/client) - enable IP forwarding
- setup
- internal gateway
- setup
wg0on interface (with a peer for external gateway) - enable IP forwarding
- enable NAT (masquerade)
- setup
- client
- setup
wg0on interface (with a peer for external gateway)
- setup
I hope it was useful. It was not meant to be a complete tutorial, I’m sure there’s a lot of details you could do differently. Feel free to experiment and customize.
Also, I haven’t discussed security at all. For example, it’s obvious that if someone gains shell access to the external gateway, he/she will be able to access hosts on the private network too. So be careful and apply appropriate security practices.
