Last active
January 1, 2026 02:22
-
-
Save WhiteHusky/680c1f80c370c87703888a79e64cfa29 to your computer and use it in GitHub Desktop.
Private WireGuard VPN for home access, including DNS and internet routing via peer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { config, lib, pkgs, ... }: | |
| { | |
| networking.firewall = { | |
| enable = true; | |
| trustedInterfaces = [ | |
| "wg-vpn" | |
| ]; | |
| allowedUDPPorts = [ | |
| # 51820 | |
| 51821 | |
| ]; | |
| }; | |
| # DNS Caching | |
| services.bind = { | |
| enable = true; | |
| cacheNetworks = [ | |
| "127.0.0.0/24" | |
| "::1/128" | |
| "10.10.10.0/24" | |
| ]; | |
| forward = "first"; | |
| forwarders = [ | |
| "9.9.9.9 tls quad9-tls" | |
| "149.112.112.112 tls quad9-tls" | |
| "2620:fe::fe tls quad9-tls" | |
| "2620:fe::9 tls quad9-tls" | |
| ]; | |
| extraOptions = '' | |
| validate-except { my.home.internal; }; | |
| ''; | |
| extraConfig = '' | |
| tls quad9-tls { remote-hostname "dns.quad9.net"; }; | |
| zone "my.home.internal" { | |
| type forward; | |
| forward only; | |
| forwarders { 10.10.10.0.101; }; | |
| }; | |
| ''; | |
| # Allows VPN devices to resolve each other for convenience. | |
| # And for example, pfSense allows setting a specific DNS server for | |
| # domains, allowing LAN devices to also find VPN devices if possible. | |
| zones."vpn.my.home.internal" = { | |
| master = true; | |
| file = pkgs.writeText "vpn.my.home.internal.zone" | |
| '' | |
| $TTL 30M | |
| @ IN SOA dns.vpn.my.home.internal. hostmaster.vpn.my.home.internal. ( | |
| 20251226 | |
| 1d | |
| 15M | |
| 2d | |
| 30M | |
| ) | |
| IN NS dns.vpn.my.home.internal. | |
| @ IN A 10.10.10.100 | |
| dns IN A 10.10.10.100 | |
| some-peer IN A 10.10.10.1 | |
| ''; | |
| }; | |
| }; | |
| networking.wireguard.interfaces."wg-vpn" = | |
| let | |
| sysctl = lib.getExe pkgs.sysctl; | |
| ns = "vpn"; | |
| ips = [ "10.10.10.100/32" ]; | |
| additional-routes = [ "10.10.10.0/24" ]; | |
| mac-address = "86:f1:b7:0f:cc:c3"; | |
| in | |
| { | |
| interfaceNamespace = ns; | |
| preSetup = '' | |
| ip netns add ${ns} | |
| ''; | |
| postSetup = lib.strings.concatStringsSep "\n" (['' | |
| ip link add ${ns} type veth peer host netns ${ns} | |
| ip link set ${ns} address ${mac-address} | |
| ip link set ${ns} arp off | |
| # Without ARP, Ethernet frames send with the source and destination | |
| # set to the sending interface's, but IP Forwarding does not act on | |
| # frames that don't match its side. So use the same one. | |
| ip -n ${ns} link set host address ${mac-address} | |
| ip -n ${ns} link set host arp off | |
| ip -n ${ns} link set host up | |
| # There's a delay, so set up again here waits. | |
| ip link set ${ns} up | |
| ''] ++ | |
| (map (ip: ''ip address add "${ip}" dev "${ns}"'') ips) ++ | |
| (map (ip: ''ip -n ${ns} route add "${ip}" dev host'') ips) ++ | |
| (map (route: ''ip route add "${route}" dev ${ns}'') additional-routes) ++ | |
| ['' | |
| ip netns exec ${ns} ${sysctl} -w net.ipv4.conf.$DEVICE.forwarding=1 | |
| ip netns exec ${ns} ${sysctl} -w net.ipv4.conf.host.forwarding=1 | |
| ''] | |
| ); | |
| postShutdown = '' | |
| # This deconstructs everything made within (or part of) the namespace | |
| ip netns delete ${ns} | |
| ''; | |
| # The initial namespace holds the IP instead, so a route is used | |
| #ips = [ XXX ] | |
| listenPort = 51821; | |
| privateKeyFile = "/etc/nixos/wg-vpn.key"; | |
| peers = [ | |
| # NAT translation could be done at this peer, unless downstream routers | |
| # are aware (and the peer allows forwarding) of the VPN subnet and routes | |
| # back up the peer via static routing. | |
| { | |
| name = "HomeFirewall"; | |
| publicKey = "AAAA"; | |
| allowedIPs = [ | |
| "10.10.10.101/32" | |
| # Home subnet | |
| "10.20.10.0/24" | |
| # Allow routing out to the internet | |
| "0.0.0.0/0" | |
| ]; | |
| } | |
| # Other devices... | |
| { | |
| name = "SomePeer"; | |
| publicKey = "BBBB"; | |
| allowedIPs = ["10.10.10.1/32"]; | |
| } | |
| ]; | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment