I’ve been running some sort of VPN to access my home network remotely for years. It probably started around the time I bought my first laptop. Back then, the only other computer I had was a Windows desktop. I wanted to access the desktop when I wasn’t home, but opening port 3389 to the world didn’t seem like such a good idea (and it turns out I was right about that), so my solution was to install an OpenSSH server for Windows and use SSH tunneling to remote in. I called this my “poor man’s VPN.”
When I got my first Raspberry Pi, I decided I’d learn how to run a proper VPN and figured out how to install OpenVPN on it (something that became very popular among RasPi enthusiasts a short time after I did it) to get full access to my whole network without having to do individual port forwards. A few years ago, I upgraded to Wireguard.
A little over a year ago the local fiber ISP, Greenlight Networks, came to my neighborhood. Everything about Greenlight has been amazing, from the fact that they spent a day digging up my yard to run their cables but left it looking almost as if they hadn’t been there that evening, to the fact that they had the entire neighborhood wired in about a week and the service activated in less than a month, to the fact that I now have a symmetric 1Gbps Internet connection for less than I was paying the cable company for 300x10Mbps. The only downside to Greenlight is that they use CGNAT, meaning no remote access, at least not over IPv4.
Greenlight does offer a routable IPv4 address for an additional $10/month, but I wanted to try something else first. When Greenlight was first activated, I decided to move my Wireguard server to a VPS server that I already had for website hosting. Then I configured a Wireguard client on my UniFi gateway to connect to the server on the VPS and set it’s AllowedIPs so that it would route traffic back to my home network. This worked great for spilt tunnel situations (letting Wireguard only route traffic for my network while everything else goes through the client’s default Internet connection), but using it as a full tunnel meant that my traffic emerged on the Internet looking like it was coming from a datacenter, not a residence, which can cause issues with some services (like streaming).
Split tunneling works fine most of the time. I have Tasker configured on my phone to connect to my VPN whenever I’m not connected to my home WiFi and it’s been working great. Since I’m not in the habit on connecting to random open WiFi networks, but instead use my phone’s data plan for the most part, split tunnelling was never a problem. As I was planning for a recent business trip, however, I started to rethink my setup. Greenlight does provide IPv6 connectivity, and that is directly routable, but my limited testing of various guest networks in places I’d visited (doing the very thing I just said I don’t do…for research), showed that lots of them don’t support IPv6.
Since most of the hotels I’ve stayed at have unencrypted guest networks (and this recent one was no exception), I want my traffic to be encrypted somehow, whether this means going through my own VPN or someone else’s (like Surfshark). I have a travel router, so this is easy enough to do. I also travel with a FireTV stick, and I’d like this to be able to access my home network so that I can access my locally stored media. This makes the Surfshark route a little more challenging.
So…could I move my Wireguard server back home and still access it, via IPv4, through the VPS? Turns out, with a bit of work, I can…
DNS and Dynamic DNS
One prerequisite for all of this to work is that you have a DNS host name for your VPS. In my case, I have a public domain name that I use for all of my self-hosted services, so I use vpn.my-self-hosted-app-domain.net for the VPN. I have this configured with an A record that points to my VPS’s IPv4 address and an AAAA record that points to my Unifi gateway’s IPv6 address. I also have a second AAAA record, mystreetname.my-self-hosted-app-domain.net pointed at my gateway’s IPv6 address, but I’m pretty sure this isn’t actually necessary. Using the name of my street as a hostname for accessing my home network is just a convention I came up with years ago, you can use whatever you want.
In BIND syntax, my DNS looks like this:
vpn.my-self-hosted-app-domain.net. 14400 IN A 12.34.56.78
vpn.my-self-hosted-app-domain.net. 600 IN AAAA 123:456:789::1
mystreetname.my-self-hosted-app-domain.net. 600 IN AAAA 123:456:789::1
In theory, my IPv6 assignment should never change, but I have no assurance of that, nor do I have reason to believe that my ISP would give me any advance warning of them changing it, so to be safe, I’ve set the TTL on my AAAA records to 10 minutes and I modified my old DIY dynamic DNS tool to support IPv6 addresses. This is completely optional and is just me being a little paranoid. You can use any dynamic DNS service that supports IPv6, though you might get stuck having to use an ugly hostname if you don’t/can’t run your own service.
To keep my UniFi gateway’s IP address updated in DNS, I need a dyndns2 client that can see the public IPv6 address and update it if it changes. I decided that the easiest way to do this was use a shell script on the gateway itself. You can access the gateway (and any other UniFi devices you have adopted) over SSH by using the password found in the Unifi Network app at Devices > Device Updates and Settings > Device SSH Authentication.
The script is pretty simple. I installed it at /persistent/bin/dyndns.
#!/bin/bash
LAST_IP=`cat /persistent/etc/last_ipv6`
CUR_IP=`ip -6 addr | grep "/128 scope global" | cut -d" " -f6 | cut -d"/" -f1`
if [[ $LAST_IP != $CUR_IP ]]; then
echo $CUR_IP >/persistent/etc/last_ipv6
URL="https://USER:PASSWORD@DYNDNS_HOST/nic/update"
RESULT=`curl -s "$URL?hostname=mystreetname.my-self-hosted-app-domain.net&myip=$CUR_IP"`
RESULT=`curl -s "$URL?hostneme=vpn.my-self-hosted-app-domain.net&myip=$CUR_IP"`
fi
The script looks up the last known IP address in a file and compares it to the public IPv6 address of the gateway. If it changed since the last run, the script will attempt to ping the dynamic DNS update service with the new IP and update the file. Be sure to change USER, PASSWORD, DYNDNS_HOST, and the hostnames with your own values.
Before you run it, you’ll need to make sure the last_ipv6 file exists and set execute permissions on the script:
mkdir /persistent/etc
touch /persistent/etc/last_ipv6
chmod 755 /persistent/bin/dyndns
Then you can run it to test it out:
/persistent/bin/dyndns
If all goes well (you should see an IPv6 address written to the /persistent/etc/last_ipv6 file and the same IP should show up in your dynamic DNS service’s settings), you can set a cron job to run every 5-10 minutes to keep an eye out for changes:
crontab -e
In the editor that opens enter:
*/5 * * * * /persistent/bin/dyndns
Once you save the file, the crontab will be activated and the updates will start.
I’m hopeful that this will stay in place during firmware updates, but I haven’t had one since setting it up, so we’ll see…
Unifi, Wireguard, and IPv6
The UniFi Gateway Max can run a servers on it, and the UniFi Network app has a very nice UI for configuring them and adding peers but, by default, it only supports IPv4. Fortunately, the Wireguard servers it starts do listen on IPv6, you just need to manually open the firewall to make it happen.
First, you need to create the VPN. This is done through Settings > VPN in the Network app. Make sure the “VPN Server” tab is selected and click “Add New.” You can pretty much leave the defaults as-is, but you might want to change a few things:
- Unifi automatically creates a new key pair for the new VPN, but If you have an existing Wireguard server and you don’t want to rekey all of your clients, you can paste in your existing public and private keys. However, since these two fields are disabled in the UI, you’ll need to go into the browser’s developer console to remove the “disabled” attribute from each field. The easiest way to do this is to right click on the field and choose “Inspect”). Then find the word “disabled” in the selected text in the window (or panel) that opens, double click on it so it becomes editable, and then press “delete.” Then go back to the main browser window and you should be able to paste in your key. Repeat for both fields.
- When adding a new client, Unifi will, by default, use the IPv4 address of the gateway as the connection address. Since you’re following this tutorial, you probably also have CGNAT and the address displayed is not Internet accessible. This is where your DNS hostname comes in: click the checkbox for “Use Alternate Address for Clients” and then enter your chosen hostname in the field that appears.
- Under “Advanced,” if you select “Manual,” you’ll be able to set a custom network address for the VPN network. This is optional–the one that UniFi selects will work fine–but if you’re moving an existing network, you’ll probably want to set this to whatever network range you’re already using.
To allow IPv6 connections to the newly created VPN, go to Settings > Policy Table and you should see an entry labeled “Allow Wireguard VPNs” This is for IPv4, but you can use it as the basis for configuring a new rule for IPv6. Make note of these settings, just in case they vary from what I have below.
To create an IPv6 rule, click “Create New Policy” and set the following values:
- Make sure the radio button for “Firewall” is selected.
- Enter a name that makes sense, like “Allow Wireguard VPNs (IPv6)”
- For “Source Zone,” choose “External” and make sure both “Any” radio buttons are selected.
- For “Action” select “Allow”
- For “Destination Zone” choose “Gateway.” Make sure “Any” is checked and select “Specific” for “Port.”
- In the “Source” list, select “Wireguard.” (If you have more than one Wireguard server running, you may need to set this to a custom port instead.)
- For “IP Version” select “IPv6.”
- For “Protocol” select “UDP”
Everything else should be fine as-is. Click “Add Policy” to save the rule.
Adding Wireguard Peers (Clients)
Go back to Settings > VPN and select your new Wireguard VPN. To add a peer click the “Add Client” link in the “Clients” section.
Unifi will do most of the work in setting up a new client for you, including creating the key and assigning it the next available IP address. You just need to either scan the QR code with your client device or click the “download” link next to it to download a conf file. You will probably also want to change the “Name” to something recognizable, but that’s up to you.
If you’re bringing an existing client over from a previous setup, click on “Manual.” Now you’ll be able to paste in the client’s existing public key and IP address. If your client will also handle routing for any networks (AllowedIPs in Wireguard terminology), you can add them as well by clicking the “Remote Client Networks” checkbox.
When you’re done adjusting your settings, be sure to scan the code or click the download link, because once you save everything, you can’t access them again. Then click “Add.” When you’re done adding clients, don’t forget to click “Apply Changes” or your new client settings will not be saved (you have no idea how many times I’ve made this mistake…it is not very obvious).
Once you have your clients configured, they should be able to connect to your VPN via IPv6.
Forwarding from IPv4
Now comes the fun part. We need a way for the VPS to receive connections and then forward each byte onto the gateway, sort of like an HTTP reverse proxy. There’s probably a bunch of ways I could do this, I chose to use a small command-line utility called socat.
socat is a pretty common tool and should be available in most Linux package distributions. On my Debian VPS, I can install it with:
sudo apt update
sudo apt install socat
The actual command I’m running is this:
socat -T 180 UDP4-LISTEN:51820,reuseaddr,fork UDP6-SENDTO:mystreetname.my-self-hosted-app-domain.net:51820,reuseaddr
The command is essentially split into two parts: what to do on the local machine (the listener) and where and how to forward the packets onto the remote machine. The -T 180 is just a timeout setting–it tells socat to drop connections after 180 seconds of no activity.
The “listener” section tells socat to listen on UDP port 51820 on IPv4 links and to “fork” any connections to another port.
UPD4_LISTEN– listen for UDP connections on IPv451820– the port to listen on (this must be the same as the port your gateway is listening on)reuseaddr–socatis allowed reuse closed connections, even if they haven’t fully timed out yetfork– this tellssocatto ask the connecting system to reconnect over a different port, keeping the initial port open for new connections. This allows for multiple connections at the same time.
The “forwarder” section is similar: it tells socat where to forward the packets it receives and how to expect to interact with the service to which it connects.
UPD6_SENDTO– send packets over UDP via IPv6mystreetname.my-self-hosted-app-domain.net– the host name or IP address of the Wireguard server (i.e. the Unifi gateway). Here I’m using themystreetnamehost name to avoid any conflicts, though I’m pretty sure just using thevpnhostname would work fine, too. If you’re not worried about IP address changes, you can use the actual IP address, just enclose it in square braces ([123:456:789::1]).51820– the port on the gateway where Wireguard is listening.reuseaddr– again,socatis allowed to reuse connections that have been closed but not fully timed outfork–socatshould expect the remote service to ask it to switch to a different port and it should comply when it does
To ensure that socat stays running when you log out, it needs to be turned in to a system service. In Debian, this is done by creating a systemd unit file.
[Unit]
Description=Socat Wireguard Forwarding
After=network-online.target
[Service]
ExecStart=/usr/bin/socat -T 180 UDP4-LISTEN:51820,reuseaddr,fork UDP6-SENDTO:mystreetname.my-self-hosted-app-domain.net:51820,reuseaddr
Restart=always
[Install]
WantedBy=multi-user.target
Create a file named socat-wireguard.service in /etc/systemd/system with the above contents. Then enable and start the service:
sudo systemctl enable socat-wireguard.service
sudo systemctl start socat-wireguard.service
Now your VPN should be available from anywhere, even without IPv6 on your client device.
Split Tunnel vs. Full Tunnel
I’ve mentioned using split tunnels earlier, but I haven’t explained how to set them up. A split tunnel VPN only handles traffic for certain IP address ranges, whereas a full tunnel VPN captures all of the client device’s traffic and routes it all through the tunnel.
With Wireguard, you can use the same key pair to do both split- and full tunneling. The config files that the Unifi Network app creates are set up for full tunneling, but with a small tweak you can switch to split.
Here’s a sample Wireguard configuration created by the Network app:
[Interface]
PrivateKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Address = 10.10.10.10/32
DNS = 10.10.10.1
[Peer]
PublicKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.my-self-hosted-app-domain.net:51820
Note the AllowedIPs line in the [Peer] section. This is the list of IP address ranges, in comma separated CIDR notation, that the VPN should handle. In this case 0.0.0.0/0 means “all traffic.” Changing it to a specific address range will limit what the VPN handles. For a typical home network, this might look like this:
AllowedIPs = 192.168.1.0/24
This will route only the traffic for devices with IP addresses of 192.168.1.*, while letting everything else go through the client’s normal Internet connection.
On most of my devices I have configurations for both full and split tunnel connections, allowing me to choose whichever connection type makes the most sense for my given situation.
Final Thoughts
This setup has been working great for me and I had no problems with routing traffic from my hotel, back to my house, and on to the Internet on my recent trip. The hotel WiFi seemed to be capped at around 30Mbps, so that, not the VPN or the crazy roundabout routing was the bottleneck of my connection.
The only downside to this setup is that, though I can connect to the VPN through IPv6, I can’t route IPv6 traffic through it. This is just a configuration limitation on Ubiquity’s part and, try as I might, I can’t find a way around it. That said, IPv6 adoption has been slow and I’ve never found a situation where something I wanted to access was only available via IPv6 so, for now, I don’t think that will be a big issue and if things change in the future, hopefully Ubiquity will also make updates to their software.
I really have no idea how robust the socat forwarder is, either. I wouldn’t trust this setup if I was dealing with hundreds or thousands of concurrent VPN connections, but if you’re dealing with that many, you hopefully have big pipe to the Internet that isn’t CGNATed in the first place. For me, where I never have more than three or four sessions open at the same time (and usually it’s more like zero or one), this is working just fine.
Another thing to watch out for when running a VPN on a VPS is data usage. VPNs can eat up a lot of data because the usage is effectively doubled: everything that comes in immediately goes right back out again. Fortunately for me, IPv4 is a last resort…most of the time I’m connecting over the cellular network, which supports IPv6, so I’m connecting directly to my gateway. My VPS provider also only meters outbound traffic, so I only get “charged” once, and my allotments are big enough that I never come close to reaching them. That might not always be the case for everyone, though, so be sure to keep an eye on your usage so you don’t get any big surprises at the end of the month.
Note: some links in this post are affiliate links. I’m may earn a small commission on the products or services if you choose to purchase them.