One of the challenges for home lab enthusiasts with connections behind CGNAT is self-hosting. Due to the limitations of double NAT, you will need to rent a cloud VPS and create a VPN tunnel between the VPS and your router or a server behind the router. Fortunately, this can be done inexpensively and with relative ease. I use Cloudfanatic as they have the unusual blend of cheap with reliable. For $4.50 USD per month it cannot be beat. Let’s get down to business.
We will be getting around the CGNAT restriction by using WireGuard. The neat thing with WireGuard is that you do not have to worry about random IP changes. This will be all updated on the WireGuard VPN endpoint. I need access to my home network so it will be routed appropriately.
Here is an overview of the topology. Public IP addresses will be represented using the standard RFC5737 192.0.2.0/24 range. This is the exact solution that I use.
[Home:CGNAT]<---------------->[VPS:192.0.2.1]
Network: 192.168.1.0/24 WG: 192.168.128.1/32
WG: 192.168.128.2/32 fd00:f1ce:fd0d:1776::1
fd00:f1ce:fd0d:1776::2
Let’s work on the VPS-side first since that is the easiest one. I am running AlmaLinux 10 on the server. In the code blocks, commands beginning with hash marks need to be run as root. The first step is to enable IP Forwarding so that traffic gets passed between the WireGuard and public interfaces.
# sysctl -w net.ipv4.ip_forward=1
# sysctl -w net.ipv6.conf.all.forwarding=1
# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
# echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
Once IP forwarding is configured, we can begin the WireGuard side of the configuration. In the next step we will be generating the VPS private and public keypair.
# dnf install epel-release wireguard-tools
# cd /etc/wireguard
# wg genkey | tee private.key | wg pubkey > public.key
# touch wg0.conf
# openssl rand -base64 32 > preshared.key
Below is my server configuration which has been sanitized. You will have to provide your specific keys. Put the config below in your wg0.conf file that you created in the previous step. While a pre-shared key is not a requirement, it is a strong recommendation because this key provides extra protection against quantum-level attacks.
[Interface]
PrivateKey = <Server Private Key>
Address = fd00:f1ce:fd0d:1776::1
Address = 192.168.128.1
ListenPort = 51820
[Peer]
PublicKey = <Home Public Key>
PresharedKey = <Your Preshared Key>
AllowedIPs = fd00:f1ce:fd0d:1776::2, 192.168.128.2/32, 192.168.1.0/24
Once this configuration has been completed, it is time to configure the firewall to allow WireGuard traffic in-bound and forwarding between the WireGuard and public interfaces. Here is how to do this.
# firewall-cmd --permanent --zone=public --add-service=wireguard
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --add-interface=wg0
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --add-forward
# firewall-cmd --reload
# firewall-cmd --permanent --zone=public --add-forward
# firewall-cmd --reload
Once the VPS-side has been configured, we can set up the machine that is going to act as the end point at home. I have a VM that is running all of the services that I self-host. This VM also acts as my WireGuard tunnel endpoint. You have an array of options but this keeps things simple. Remember to copy the public key from your home end point to the VPS configuration and vice versa. Please do the same with the pre-shared key.
The first and most important step is to enable routing on the home endpoint.
# sysctl -w net.ipv4.ip_forward=1
# sysctl -w net.ipv6.conf.all.forwarding=1
# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
# echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
Install WireGuard
# dnf install wireguard-tools
# cd /etc/wireguard
# wg genkey | tee private.key | wg pubkey > public.key
# touch wg0.conf
Configure WireGuard.
[Interface]
PrivateKey = <Home Private Key>
Address = fd00:f1ce:fd0d:1776::2
Address = 192.168.128.2/32
[Peer]
PublicKey = <Server Public Key>
PresharedKey = <Pre-shared Key>
AllowedIPs = fd00:f1ce:fd0d:1776::1, 192.168.128.1/32
Endpoint = 192.0.2.1:51820
PersistentKeepalive = 25
The firewall on the home side of the WireGuard tunnel is more complex because NAT must be enabled so that communication works bi-directionally.
# firewall-cmd --permanent --zone=trusted --new-policy=wg-to-lan
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --add-interface=wg0
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --policy=wg-to-lan --add-ingress-zone=trusted
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --policy=wg-to-lan --add-egress-zone=public
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --policy=wg-to-lan --set-target ACCEPT
# firewall-cmd --reload
# firewall-cmd --permanent --zone=trusted --add-forward
# firewall-cmd --reload
# firewall-cmd --permanent --zone=public --add-masquerade
# firewall-cmd --reload
# firewall-cmd --permanent --zone=public --add-forward
# firewall-cmd --reload
Now we can bring the tunnel up. Do the following on both the VPS and the home sides.
# systemctl enable --now wg-quick@wg0
Once the tunnel is brought up, we can do some verification and testing. On the home side, you should see something similar to the following:
# sudo wg
interface: wg0
public key: <Home Public Key>
private key: (hidden)
listening port: 58512
peer: <VPS Public Key>
preshared key: (hidden)
endpoint: 192.0.2.1:51820
allowed ips: fd00:f1ce:fd0d:1776::1/128, 192.168.128.1/32
latest handshake: 1 minute, 1 second ago
transfer: 49.61 MiB received, 438.12 MiB sent
persistent keepalive: every 25 seconds
# ping -c 5 192.168.128.1
PING 192.168.128.1 (192.168.128.1) 56(84) bytes of data.
64 bytes from 192.168.128.1: icmp_seq=1 ttl=64 time=19.4 ms
64 bytes from 192.168.128.1: icmp_seq=2 ttl=64 time=19.7 ms
64 bytes from 192.168.128.1: icmp_seq=3 ttl=64 time=18.0 ms
64 bytes from 192.168.128.1: icmp_seq=4 ttl=64 time=18.3 ms
64 bytes from 192.168.128.1: icmp_seq=5 ttl=64 time=22.9 ms
--- 192.168.128.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 17.993/19.646/22.872/1.737 ms
On the VPS, do the same thing:
# wg
interface: wg0
public key: <VPS Public Key>
private key: (hidden)
listening port: 51820
peer: <Home Public Key>
preshared key: (hidden)
endpoint: <Home IP>:58512
allowed ips: fd00:f1ce:fd0d:1776::2/128, 192.168.128.2/32, 192.168.1.0/24
latest handshake: 45 seconds ago
transfer: 2.27 GiB received, 259.69 MiB sent
# ping -c 5 192.168.128.2
PING 192.168.128.2 (192.168.128.2) 56(84) bytes of data.
64 bytes from 192.168.128.2: icmp_seq=1 ttl=64 time=18.0 ms
64 bytes from 192.168.128.2: icmp_seq=2 ttl=64 time=18.0 ms
64 bytes from 192.168.128.2: icmp_seq=3 ttl=64 time=18.2 ms
64 bytes from 192.168.128.2: icmp_seq=4 ttl=64 time=19.2 ms
64 bytes from 192.168.128.2: icmp_seq=5 ttl=64 time=19.3 ms
--- 192.168.128.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4005ms
rtt min/avg/max/mdev = 17.961/18.527/19.331/0.603 ms
If you get results similar to the ones above, than the tunnel itself has been established between the two end points. The next step is to see if the VPS can reach your home network. The address I am pinging here represents my default gateway on the LAN. Again, you should see results similar to the ones below.
# ping -c 5 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=22.0 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=20.6 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=63 time=20.6 ms
64 bytes from 192.168.1.1: icmp_seq=4 ttl=63 time=20.2 ms
64 bytes from 192.168.1.1: icmp_seq=5 ttl=63 time=20.0 ms
--- 192.168.1.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 20.031/20.694/22.026/0.707 ms
From here, the sky is the limit. On the VPS, side though you will need to set up some port forwarding for access to services that are not http/https related. If you intend to host a website, use your favorite reverse proxy app. I use NGINX but configuring this is beyond the scope of the post. Below sets up port forwarding for SSH so you can remotely access your network.
# firewall-cmd --permanent --zone=public --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=192.168.128.2

Leave a Reply