Upon connecting to the VPN, NetworkManager will setup 'ip rule's that will cause all you traffic to go through the 'wg0' interface. The last part of the puzzle is to configure the VPS networking so that it injects the traffic found on its own 'wg0' (the other side of the WireGuard tunnel) into its own network, as if it was from itself. After some research, the important bits to achieve this is IP forwarding, which is disabled in Linux by default (at least for Guix System's configuration), as well as masquerading, which is source NAT (rewriting the source address), which can be done with netfilter (nft). The most critical parts of the nftables "rule set" made to achieve this are, as an input chain filter on the inet table: # forward all packets from WireGuard VPN to WAN iifname $wg_iface oifname $pub_iface accept as well as # masquerade all packets from WireGuard VPN to the WAN iifname $wg_iface oifname $pub_iface masquerade as a forward chain filter on that same inet table. The complete configuration can be seen below: --8<---------------cut here---------------start------------->8--- ;; -*- mode: scheme; -*- ;; This is an operating system configuration template ;; for an OVH virtual private server (VPS), with no X11 display server. (use-modules (gnu)) (use-service-modules networking ssh sysctl vpn) (use-package-modules ssh) (define %username "your-user") (define %system (operating-system (host-name "vps-NNNN") (locale "en_US.utf8") (bootloader (bootloader-configuration (bootloader grub-bootloader) (targets '("/dev/sda")))) (kernel-arguments (list "console=ttyS0 console=tty0")) (file-systems (cons* (file-system (device (uuid "bbf61fb4-b6ce-44af-ac57-1850cd708965")) (mount-point "/") (type "btrfs") (options "compress=zstd")) %base-file-systems)) (initrd-modules (cons "virtio_scsi" ; Needed to find the disk %base-initrd-modules)) ;; This is where user accounts are specified. The "root" ;; account is implicit, and is initially created with the ;; empty password. (users (cons (user-account (name %username) (group "users") ;; Adding the account to the "wheel" group ;; makes it a sudoer. Adding it to "audio" ;; and "video" allows the user to play sound ;; and access the webcam. (supplementary-groups '("wheel"))) %base-user-accounts)) ;; Add services to the baseline: a DHCP client and an SSH ;; server. You may wish to add an NTP service here. (services (append (list (service dhcp-client-service-type) ;for IPv4 (service static-networking-service-type (list (static-networking (provision '(networking-v6)) (addresses (list (network-address (device "eth0") (value "2607:5300:xxxx:xxxx::aaaa/64")))) (routes (list (network-route (device "eth0") (destination "default") (gateway "2607:5300:xxxx:xxxx::1"))))))) (service openssh-service-type (openssh-configuration (openssh openssh-sans-x) (port-number 2222) (password-authentication? #f) (authorized-keys `((,%username ,(plain-file "key.pub" "ssh-ed25519 $MY-PUB-SSH-KEY")))))) ;; This implements a 'point to site' Wireguard VPN config, ;; where site is the WAN in our case (see: ;; https://www.procustodibus.com/blog/2020/11/wireguard-point-to-site-config/, ;; https://www.procustodibus.com/blog/2021/11/wireguard-nftables/) (service wireguard-service-type (wireguard-configuration (peers (list (wireguard-peer (name "terra") (public-key "XXXXXXXXXXX=") (allowed-ips '("10.0.0.3/32" "fdb5:995f:152c::3/128"))) (wireguard-peer (name "x200-laptop") (public-key "YYYYYYYYYYY=") (allowed-ips '("10.0.0.2/32" "fdb5:995f:152c::2/128"))))))) (service nftables-service-type (nftables-configuration (ruleset (plain-file "nftables.conf" "\ # A simple and safe firewall (based on %default-nftables-ruleset) define pub_iface = \"eth0\" define wg_iface = \"wg0\" define wg_port = 51820 define ssh_port = 2222 table inet filter { chain input { type filter hook input priority 0; policy drop; # early drop of invalid connections ct state invalid drop # allow established/related connections ct state { established, related } accept # allow from loopback iif lo accept # drop connections to lo not coming from lo iif != lo ip daddr 127.0.0.1/8 drop iif != lo ip6 daddr ::1/128 drop # allow icmp ip protocol icmp accept ip6 nexthdr icmpv6 accept # allow ssh tcp dport $ssh_port accept # allow wireguard port udp dport $wg_port accept # reject everything else reject with icmpx type port-unreachable } chain forward { type filter hook forward priority 0; policy drop; # forward all packets that are part of an already-established connection ct state vmap { invalid: drop, established: accept, related: accept } # forward all packets from WireGuard VPN to WAN iifname $wg_iface oifname $pub_iface accept # allow traffic between WireGuard peers iifname $wg_iface oifname $wg_iface accept # reject with polite 'host unreachable' icmp response reject with icmpx type host-unreachable } chain output { type filter hook output priority 0; policy accept; } } table inet nat { chain postrouting { type nat hook postrouting priority 100; policy accept; # masquerade all packets from WireGuard VPN to the WAN iifname $wg_iface oifname $pub_iface masquerade } } "))))) (modify-services %base-services (guix-service-type config => (guix-configuration (inherit config) (authorized-keys (cons* (local-file "x200-signing-key.pub") (local-file "terra-signing-key.pub") %default-authorized-guix-keys)))) (sysctl-service-type config => (sysctl-configuration (settings ;; Enable IP-forwarding, for the WireGuard VPN. (cons* '("net.ipv4.ip_forward" . "1") '("net.ipv6.conf.all.forwarding" . "1") (sysctl-configuration-settings config)))))))) (sudoers-file (plain-file "sudoers" (string-append (plain-file-content %sudoers-specification) (format #f "~a ALL = NOPASSWD: ALL~%" %username)))))) (list (machine (operating-system %system) (environment managed-host-environment-type) (configuration (machine-ssh-configuration (host-name "$my-vps-hostname") (port 2222) (user %username) (host-key "ssh-ed25519 $my-host-key") (system "x86_64-linux"))))) --8<---------------cut here---------------end--------------->8--- Hopefully this long text is useful to someone :-) Happy hacking! -- Maxim