Hello,
Recently, I’ve been interested in self-hosting various services after coming across Futo’s “How to Self Host Your Life Guide” on their Wiki. They recommend using OpenVPN, but I opted for WireGuard instead as I wanted to learn more about it. After investing many hours into setting up my WireGuard configuration in my Nix config, I planned to replace Tailscale with WireGuard and make the setup declarative.
For context, this computer is located at my residence, and I want to be able to VPN into my home network and access my services. Initially, it was quite straightforward; I forwarded a UDP port on my router to my computer, which responded correctly when using the correct WireGuard keys and established a VPN connection. Everywhere online suggests forwarding only UDP as WireGuard doesn’t respond unless the correct key is used.
The Networking Complexity
At first, this setup would be for personal use only, but I soon realized that I had created a Docker stack for me and my friends to play on a Minecraft server running on my LAN using Tailscale as the network host. This allowed them to VPN in and join the server seamlessly. However, I grew tired of having to log in to various accounts (e.g., GitHub, Microsoft, Apple) and dealing with frequent sign-outs due to timeouts or playing around with container stacks.
To manage access to my services, I set up ACLs using Tailscale, allowing only specific IP addresses on my network (192.168.8.170) to access HigherGround, nothing else. Recently, I implemented WireGuard and learned two key things: Firstly, when friends VPN into the server, they have full access to everything, which isn’t ideal by no means. not that i dont trust my friends but, i would like to fix that :P. I then tried to set allowed IPs in the WireGuard config to 192.168.8.170, but realized that this means they can only access 192.168.8.170 explicitly, not being able to browse the internet or communicate via Signal until I added their specific IP addresses (10.0.0.2 and 10.0.0.3) to their WireGuard configs.
However, I still face a significant issue: every search they perform goes through my IP address instead of theirs.
The Research
I’ve researched this problem extensively and believe that split tunneling is the solution: I need to configure the setup so that only 192.168.8.170 gets routed through the VPN, while all other traffic is handled by their local router instead of mine. Ideally, my device should be able to access everything on the LAN and automatically route certain traffic through a VPS (like accessing HigherGround), but when performing general internet tasks (e.g., searching for “how to make a sandwich”), it gets routed from my router to ProtonVPN.
I’ve managed to get ProtonVPN working, but still struggle with integrating WireGuard on my phone to work with ProtonVPN on the server. From what I’ve read, using iptables and creating specific rules might be necessary to allow only certain devices to access 192.168.8.170 (HigherGround) while keeping their local internet traffic separate.
My long-term goal is to configure this setup so that my friends’ local traffic remains on their network, but for HigherGround services, it routes through the VPN tunnel or ProtonVPN if necessary.
My nix Config for wiregaurd (please let me know if im being stoopid with somthing networking is HARRRD)
#WIREGAURD connect to higher ground networking.wg-quick.interfaces = { # “wg0” is the network interface name. You can name the interface arbitrarily. caveout0 = { #Goes to ProtonVPN address = [ “10.2.0.2/32” ]; dns = [ “10.2.0.1” ]; privateKeyFile = “/root/wiregaurd/privatekey”; peers = [ { #From HigherGround to Proton publicKey = “magic numbers and letters”; allowedIPs = [ “0.0.0.0/0” “::/0” ]; endpoint = “79.135.104.37:51820”; persistentKeepalive = 25; } ]; };
cavein0 = { # Determines the IP/IPv6 address and subnet of the client’s end of the tunnel interface address = [ “10.0.0.1/24” ]; dns = [ “192.168.8.1” “9.9.9.9” ]; # The port that WireGuard listens to - recommended that this be changed from default listenPort = 51820; # Path to the server’s private key privateKeyFile = “magic numbers and letters”;
# This allows the wireguard server to route your traffic to the internet and hence be like a VPN
postUp = ''
${pkgs.iptables}/bin/iptables -A FORWARD -i cavein0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -o enp5s0 -j MASQUERADE
'';
# Undo the above
preDown = ''
${pkgs.iptables}/bin/iptables -D FORWARD -i cavein0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -o enp5s0 -j MASQUERADE
'';
peers = [
{ #friend1
publicKey = "magic numbers and letters";
allowedIPs = [ "10.0.0.3/32" "192.168.8.170/24" ];
endpoint = "magic numbers and letters";
presharedKey = "magic numbers and letters";
persistentKeepalive = 25;
}
{ # My phone
publicKey = "magic numbers and letters";
allowedIPs = [ "10.0.0.2/32" ];
endpoint = "magic numbers and letters";
presharedKey = "magic numbers and letters";
persistentKeepalive = 25;
}
{# friend 2
publicKey = "magic numbers and letters";
allowedIPs = [ "10.0.0.4/32" "192.168.8.170/24" ];
endpoint = "magic numbers and letters";
presharedKey = "magic numbers and letters";
persistentKeepalive = 25;
}
{# friend 3
publicKey = "magic numbers and letters";
allowedIPs = [ "10.0.0.5/32" ];
endpoint = "magic numbers and letters";
presharedKey = "magic numbers and letters";
persistentKeepalive = 25;
}
# More peers can be added here.
];
};
};
#Enable NAT networking.nat = { enable = true; enableIPv6 = false; externalInterface = “enp5s0”; internalInterfaces = [ “cavein0” ]; };
services.dnsmasq.settings = { enable = true; extraConfig = ‘’ interface=cavein0 ‘’; };
Any help would be appreciated thanks
References: Futo Wiki: https://wiki.futo.org/index.php/Introduction_to_a_Self_Managed_Life:_a_13_hour_%26_28_minute_presentation_by_FUTO_software
NixOS Wireguard: https://wiki.nixos.org/w/index.php?title=WireGuard&mobileaction=toggle_view_desktop
Just a FYI, the main portion of the paragraph was put into llama3.1 with the prompt “take the following prompt and fix the grammer, spelling and spacing to make it more readable” Because im bad at english and didnt want to pain people with my choppy sentences and poor grammer
Old Client Config
Solution somewhat found! so i didnt understand what wireguard allowIPS really did, well i did but it was confusing. So what i did before was have 10.0.0.2/32 only, this allowed users of the VPS to have acess to my local network. i swapped it to where there was only 192.168.8.170 only and that made it to where i could ONLY acess the service and no other webpage or dns. the solution was to set on the server side, for peers allowed ip adresses to be “192.168.8.170/24” and "10.0.0.2/32, this allows each user to have there own IP adress within the server. so for example my phone has 10.0.0.2/32 and 192.168.8.170. THE CLIENT SIDE MUST MATCH!!! Which is what i missed before, my guess on why this is important is so your network manager on whatever your client os is running, knows that it can only acess 192.168.8.170 and anything within the 10.0.0.2/32 subnet. The reason why you NEED 10.0.0.2/32 is so the client can have an ip adress to talk to the server internally. at least i think im just a guy who dicks around with pc’s in his free time :P.
so having 192.168.8.170/24 and 10.0.0.2/32 on both the wireguard client config and the server enforces that the client cannot acess anything but those adresses and subnets.
i still would like to setup split tunneling, because on my server if i wanna VPN from my server to protonVPN my wiregaurd server doesnt connect. but im glad i got it to this state, thanks for helping out everybody :)
All answers should be given in mspaint
gronk use ms paint to describe. gronk bad at explaining so he draws pretty pictures :P
You need more Excalidraw in your life.
neat web app! the drawings kinda suck tho, but thats just a skill issue on my part :P
Is your friends client config setup for split tunneling?
If the client config installs a default route to the VPN you will get all their traffic.
The server config can refuse to forward traffic to the internet, but then your friends will be effectively on your LAN but otherwise appear to have no internet access.
Its a good idea to restrict which IPs on your LAN they can access, but the client config also needs route only those IPs over the VPN.
https://www.privateproxyguide.com/how-to-set-up-split-tunneling-with-wireguard/
so my friends wiregaurd config is
Old Client Config
"[Interface] Address = 10.0.0.3/24 ListenPort = 51820 PrivateKey = magic numbers
[Peer] PublicKey = magic numbers PresharedKey = magic numbers AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = magic numbers"
So if i understand the article correctly, i need to change it to
New client config
"[Interface] Address = 10.0.0.3/24 ListenPort = 51820 PrivateKey = magic numbers
[Peer] PublicKey = magic numbers
PresharedKey = magic numbers
AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = magic numbers" Split tunneling: Exclude certain traffic from the VPN
PostUp = ip rule add from 192.168.50.0/24 table main
PostDown = ip rule delete from 192.168.50.0/24 table main
my friends LAN is 192.168.50.0/0 so im assuming were just trying to tell wireguard that anything within my friends subnet doesnt get routed? which means he will still be able to reach HigherGround@'192.168.8.170? and all of his other traffic will be local to him and go through his router?
im confused what “table” and “main” are im assuming its apart of iptables rules? im pretty new to IP tables so forgive me for my lack of understanding. i know its basically a linux purest firewall LMAO,
Then on my server i would edit
This allows the wireguard server to route your traffic to the internet and hence be like a VPN
postUp = '' ${pkgs.iptables}/bin/iptables -A FORWARD -i cavein0 -j ACCEPT ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -o enp5s0 -j MASQUERADE ''; # Undo the above preDown = '' ${pkgs.iptables}/bin/iptables -D FORWARD -i cavein0 -j ACCEPT ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -o enp5s0 -j MASQUERADE '';
And make it like this?
This allows the wireguard server to route your traffic to the internet and hence be like a VPN
postUp = '' ${pkgs.iptables}/bin/iptables -A FORWARD -i cavein0 -j ACCEPT ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -o enp5s0 -j MASQUERADE ${pkgs.busybox/bin/ip rule add from 192.168.50.0/24 table main ''; # Undo the above preDown = '' ip rule add from 192.168.1.0/24 table main ${pkgs.iptables}/bin/iptables -D FORWARD -i cavein0 -j ACCEPT ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -o enp5s0 -j MASQUERADE ${pkgs.busybox/bin/ip rule delete from 192.168.50.0/24 table main '';
Right? or is step 4 on the client still? its not very clear in the article thanks for helping out!
Well you’ve definitely overcomplicated things by introducing a lot of different variables into the mix. What you’ve set up is a hat on a hat. An abstraction on top of another abstraction.
Let me break it down and just ask: are you just trying to ensure your friends can only access certain addresses on your network, and you’re using Tailscale ACLs for that purpose? It sounds like you just want proper routing security at the edge of your network and are going this route to avoid having to do that maybe?
Tailscale has a place and works great for lots of things, but using it like this is going to cause all kinds of problems eventually. You’re basically just advertising a bunch of confused routes to the coordinator like this, and DERP connections will eventually fail or freak out because you’re providing multiple paths to different things if I’m reading this right. You’re also introducing A LOT of network overhead because of this, and I can forsee a lot of connectivity issues in gaming throughput if that’s the main purpose.
ok ill try to explain to the best of my ability and simply it.
i no longer want to use tailscale, because of accounts. i used to use tailscale for the minecraft server i want my friends to be able to acess only 192.168.8.170 on my local network and all other traffic to not be routed through my vpn but my friends to have acess to there internet on there LAN. example, we can play minecraft on the server on my network and we can be in a group call in signal. meaning friend 1 and 2 are using there internet connection locally, and only 192.168.8.170 being routed.
We also had some connectivity issues with tailscale, where friend 1 would be on and friend 2 would lag out of the server randomly. when if we played a game through steam we wouldnt have any connection issues. my friend is also very forgetful and cant log into his tailscale account, which is another reason why i wanna ditch tailscale.
Take a look at Netbird https://netbird.io/. It’s wireguard based, it works better if you run it on server out in the world, but you can run it locally if you punch a hole in your firewall.
Okay then if Wireguard, you need to punch through your NAT and allow that port access to the world, then to fix your outing issue, you need to look up split-tunneling. That should solve the issues you’re seeing if I’m reading this right.
Iirc setting AllowedIPs in your clients config should do exactly that. Their respective [Peer] entry in your servers config should also have the same AllowedIPs, otherwise they could easily circumvent this
Further finetuning should be really easy by using any firewall on your Wireguard server
If you want your clients to be able to access other devices in your servers LAN you need to setup additional packet forwarding rules though. Optionally setup NAT aswell
so for example, setting 192.168.8.170 on the clients as the only allowed IP aswell as the server would do what i need? thanks for helping trying to navigate my labyrinth of networking :P
For client example [Interface] Address = 10.0.0.3/24 ListenPort = 51820 PrivateKey = magic numbers
[Peer] PublicKey = magic numbers
PresharedKey = magic numbers
AllowedIPs = 192.168.8.170 Endpoint = magic numbers"
For server example
peers = [
{ #friend1 publicKey = “magic numbers and letters”; allowedIPs = [ “192.168.8.170/24” ]; endpoint = “magic numbers and letters”; presharedKey = “magic numbers and letters”; persistentKeepalive = 25; }
{ # My phone publicKey = “magic numbers and letters”; allowedIPs = [ “192.168.8.170/24” ]; endpoint = “magic numbers and letters”; presharedKey = “magic numbers and letters”; persistentKeepalive = 25; }
{# friend 2 publicKey = “magic numbers and letters”; allowedIPs = [ “192.168.8.170/24” ]; endpoint = “magic numbers and letters”; presharedKey = “magic numbers and letters”; persistentKeepalive = 25;
} {# friend 3 publicKey = “magic numbers and letters”; allowedIPs = [ “192.168.8.170/24” ]; endpoint = “magic numbers and letters”; presharedKey = “magic numbers and letters”; persistentKeepalive = 25; }
No, AllowedIPs should be set to your internal Wireguards IP range to only allow access to your Wireguard peers. You could also add more like your Servers LAN for example (which will need packet forwarding, as I mentioned before)
Here’s an example of one of my client configs:
[Interface] Address = 10.8.0.2/32 PrivateKey = [Peer] PublicKey = PresharedKey = Endpoint = 192.168.0.3:51820 AllowedIPs = 10.8.0.0/16
Just be careful to not mess up your subnet masks. For example my [Interface] Address ends with /32 because that only leaves 10.8.0.2 In the [Peer] Section i set it to /16 which will allow the client to connect to 10.8.x.x iirc
Best is to just try it yourself and see if it works, I’m by no means good at networking stuff
You convinced me I need to appreciate tailscale somehow even more than I do.
services.tailscale.enable = true;
Check this project https://github.com/whyvl/wireproxy
so if i understand this correctly, it runs a docker container to which runs the wireguard server and then you just specify hostname/ip adress for the services, then when sombody tunnels in they have acess to only the services specifide in config file? if so looks pretty useful!, i just question what happens to the rest of the traffic? is it locally routed in my network or client side? im not very familiar with proxys, i know what they are but have never really messed with one. Thanks for sharing
To be honest I don’t really know, but I know that what you want can easily be solved with SOCKS5 proxy. I think Wireguard and other VPNs are added to encrypt the traffic. There are also other alternatives to SOCKS5 proxy adding encryption.
In Wireguard you have those Allowed IPs, you can allow only those IPs to be reachable from outside and you can configure them per client if I am not wrong. I think the easiest way would be for you to run those services over Docker, that way each server will have an IP from your docker network and you can isolate the traffic. https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/
My personal suggestion is to spin up a VM, install Debian, Ubuntu, or whatever your poison is, run docker compose or podman compose, spring up a Docker or two and Wireguard and try to achieve what you want. Heck you can even run Wireguard from a container. Once confident with your setup you can migrate it to Nix.