Wireguard client with Linuxserver.io wireguard server

Well - thanks for following along. I’m not sure I totally get what wasn’t working or now why it’s working just fine. Moving from the networking.wireguard.interfaces to networking.wg-quick.interfaces just fixed me.

Here is a recap - and I’ll tag this as the ‘solution’.

  1. I’m running the linuxserver.io wireguard container on a machine I’ll call the server
  2. I have a second machine running NIxOS which I will call the client

My server is configured as I wrote up here - having wireguard setup has been amazing, I would highly recommend it.

This post is about managing a NixOS client which lives at a different physical location than the server, and has a completely different internet connection. I first tried the documented client setup - and ran into problems - as you can read in this thread.

FWIW - the client is a desktop install of NixOS. While it is currently headless, if you plug in a monitor you’ll get the full gnome desktop experience.

My solution ended up being simple. I just followed the client setup for wg-quick

I take the generated peer_client.conf file that I generate using the linuxserer.io container that is running on my server. It looks something like this:

$cat peer_client.conf 
[Interface]
Address = 10.13.13.9
PrivateKey = SECRET111XXX=
ListenPort = 51820
DNS = 192.168.1.8

[Peer]
PublicKey = SECRET222XXX=
PresharedKey = SECRET333XXX=
Endpoint = example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0

And map it into the client nixos configuration file

  networking.firewall = {
    allowedUDPPorts = [ 51820 ]; # wireguard
  };

  # Enable WireGuard
  networking.wg-quick.interfaces = {
    # "wg0" is the network interface name. You can name the interface arbitrarily.
    wg0 = {
      # Determines the IP address and subnet of the client's end of the tunnel interface.
      address = [ "10.13.13.9/32" ];
      listenPort = 51820; # to match firewall allowedUDPPorts (without this wg uses random port numbers)
      privateKeyFile = "/etc/nixos/secrets/privatekey.wg";

      peers = [
        {
          # Public key of the server (not a file path).
          publicKey = "SECRET222XXX=";

          # Pre-shared key file
          presharedKeyFile = "/etc/nixos/secrets/preshare.wg";

          # Forward only particular subnets
          allowedIPs = [ "192.168.1.0/22" ];

          # Set this to the server IP and port.
          endpoint = "example.com:51820"; 

          # Send keepalives every 25 seconds. Important to keep NAT tables alive.
          persistentKeepalive = 25;
        }
      ];
    };
  };

You will notice I’ve got the firewall allowedUDPPorts I’m not sure this is required, but I had it from before and it seems to at least be harmless. I’m also specifying a listenPort which may not be required, but again doesn’t seem to hurt.

At this point - it all just works. I don’t see any particular errors in the logs. I can reach back from the server to the client

me@server:~$ ping 10.13.13.9
PING 10.13.13.9 (10.13.13.9) 56(84) bytes of data.
64 bytes from 10.13.13.9: icmp_seq=1 ttl=63 time=30.7 ms
64 bytes from 10.13.13.9: icmp_seq=2 ttl=63 time=29.6 ms
^C
--- 10.13.13.9 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 29.633/30.188/30.743/0.555 ms

I can even ssh ‘back’ over the wireguard link. My client location has zero ports opened in the firewall – but from that remote client I can resolve things on my local (to the server) network.

This is pretty awesome. Sure I could just start using tailscale – but this is all 100% self hosted (yes, I’m also aware of headscale) – and it has only a few moving parts.

In the end - this setup does exactly what I set out to do. Woo hoo!