WireGuard VPN
nixlab includes a WireGuard VPN server running as a Kubernetes pod on the master node. It provides encrypted remote access to your homelab and optional automatic login to Nextcloud for VPN users.
How it works
The WireGuard pod runs two containers:
- wireguard: the VPN server on UDP port 51820 (
vars.wireguardIp) - caddy sidecar: an HTTPS proxy on TCP port 443 that injects
X-Remote-Userheaders for Nextcloud SSO
When a VPN user connects to Nextcloud from within the tunnel, Pi-hole resolves nextcloud.<vars.domain> to 10.0.100.1 (the WireGuard server's VPN IP) instead of the nginx ingress IP. The request hits Caddy, which identifies the user by their VPN IP, injects the X-Remote-User header with their Nextcloud username, and proxies to Nextcloud. Nextcloud trusts this header and logs the user in automatically.
LAN users (not on VPN) resolve nextcloud.<vars.domain> to the nginx ingress and go through normal authentication.
VPN subnet
The VPN uses 10.0.100.0/24:
10.0.100.1- WireGuard server (caddy sidecar also listens here)10.0.100.2and up - clients (assigned per user invars.wireguardUsers)
DNS for VPN clients is vars.piholeIp - Pi-hole blocks ads and resolves local hostnames for VPN users the same as LAN users.
Adding a user with add-wg-user.sh
The script at modules/system/sops/add-wg-user.sh automates the full onboarding flow:
cd nixlab
bash modules/system/sops/add-wg-user.sh <username>
What it does:
- Reads
vars.wireguardUsersvianix evalto find used IPs. - Picks the next free IP in
10.0.100.0/24. - Generates a WireGuard keypair with
wg genkey. - Stores the public and private keys in
modules/system/sops/secrets.yamlviasops --set. - Reads the server public key and endpoint from SOPS.
- Prints a ready-to-use client config and the
vars.nixsnippet to add.
Example output:
Client config for alice
============================
[Interface]
PrivateKey = <alice-private-key>
Address = 10.0.100.2/32
DNS = 192.168.1.250
[Peer]
PublicKey = <server-public-key>
Endpoint = <your-public-ip>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Add to vars.nix wireguardUsers
============================
"alice" = {
ip = "10.0.100.2";
publicKeySecret = "alice_wg_public_key";
allowedIPs = "0.0.0.0/0";
enabled = true;
};
After running the script:
-
Paste the
vars.nixsnippet intowireguardUsers, addinggroup,description, and optionallynextcloudUser:wireguardUsers = { "alice" = { ip = "10.0.100.2"; group = "admin"; publicKeySecret = "alice_wg_public_key"; allowedIPs = "0.0.0.0/0"; nextcloudUser = "alice"; # optional: enables Nextcloud SSO description = "Alice - full access"; enabled = true; }; }; -
Deploy to the master:
colmena apply --on @masterThe activation script updates the WireGuard ConfigMap with the new peer. The
k8s-deployservice restarts the WireGuard pod to apply the new config. -
Send the client config to the user (the block printed by the script). The private key is already embedded - the user just imports it.
Retrieving a user's private key later
The private key is stored in SOPS. To recover it:
sops --decrypt --extract '["alice_wg_private_key"]' modules/system/sops/secrets.yaml
Disabling a user
Set enabled = false in vars.nix:
"alice" = {
...
enabled = false;
};
Deploy: colmena apply --on @master
The user's public key is removed from the WireGuard server config and their SOPS secret is unregistered from NixOS. The secret entry in secrets.yaml is left in place (to preserve the key if you re-enable the user).
Removing a user entirely
- Set
enabled = falseand deploy to confirm the peer is removed. - Remove the user's entry from
vars.wireguardUsersinvars.nix. - Remove their keys from
secrets.yaml:sops modules/system/sops/secrets.yaml # delete the alice_wg_public_key and alice_wg_private_key entries - Deploy:
colmena apply --on @master
Client setup
Linux (wg-quick)
# Save the config from add-wg-user.sh output as:
sudo mkdir -p /etc/wireguard
sudo nano /etc/wireguard/nixlab.conf # paste the [Interface] + [Peer] block
# Connect
sudo wg-quick up nixlab
# Disconnect
sudo wg-quick down nixlab
# Auto-start on boot
sudo systemctl enable wg-quick@nixlab
macOS
Install the WireGuard app from the App Store. Click the + button and import the config file (save the output of add-wg-user.sh as a .conf file).
iOS / Android
Install the WireGuard app from the App Store or Google Play. Use the QR code option - generate a QR code from the config on your workstation:
# Install qrencode
nix run nixpkgs#qrencode -- -t ansiutf8 < alice.conf
Or use the app's "Import from file" option.
Windows
Install WireGuard for Windows. Use "Import tunnel(s) from file" and select the .conf file.
Access groups
The group field in vars.wireguardUsers is a label - it doesn't currently enforce any network policy. It's intended for documentation and future use (e.g., network policies to restrict which cluster services different groups can reach).
Suggested conventions:
admin- full access, including Nextcloud SSOfamily- homelab services, limited external routingfriends- split tunnel, homelab access onlyguests- internet-only via VPN (no homelab access)
To implement network isolation between groups, add Kubernetes NetworkPolicy resources in the relevant namespaces based on the source VPN IP ranges for each group.
Nextcloud SSO mechanics
The caddy sidecar's Caddyfile is generated at build time from vars.wireguardUsers. For each user with nextcloudUser set, it generates a block like:
@alice remote_ip 10.0.100.2
handle @alice {
reverse_proxy http://nextcloud.nextcloud.svc.cluster.local:8080 {
header_up Host nextcloud.yourdomain.example.com
header_up X-Remote-User "alice"
}
}
Requests from unrecognized VPN IPs get a 403. VPN users without nextcloudUser set will receive a 403 when accessing Nextcloud over the VPN - they should use the normal LAN or internet path instead.
The Nextcloud Helm chart is configured with:
'trusted_proxies' => ['10.42.0.0/16'],
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],
This makes Nextcloud trust the X-Remote-User header when it comes from the k3s pod CIDR - where caddy runs.