{"id":551,"date":"2026-04-23T09:08:50","date_gmt":"2026-04-23T13:08:50","guid":{"rendered":"https:\/\/goblackcat.com\/?p=551"},"modified":"2026-04-23T09:08:50","modified_gmt":"2026-04-23T13:08:50","slug":"cgnat-and-self-hosting","status":"publish","type":"post","link":"https:\/\/goblackcat.com\/wordpress\/2026\/04\/23\/cgnat-and-self-hosting\/","title":{"rendered":"CGNAT and Self-Hosting"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">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 <a href=\"https:\/\/www.cloudfanatic.com\">Cloudfanatic<\/a> as they have the unusual blend of cheap with reliable. For $4.50 USD per month it cannot be beat. Let&#8217;s get down to business.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Home:CGNAT]&lt;----------------&gt;&#91;VPS:192.0.2.1]\nNetwork: 192.168.1.0\/24       WG: 192.168.128.1\/32       \nWG: 192.168.128.2\/32              fd00:f1ce:fd0d:1776::1\n    fd00:f1ce:fd0d:1776::2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># sysctl -w net.ipv4.ip_forward=1\n# sysctl -w net.ipv6.conf.all.forwarding=1\n# echo \"net.ipv4.ip_forward=1\" &gt;&gt; \/etc\/sysctl.conf\n# echo \"net.ipv6.conf.all.forwarding=1\" &gt;&gt;     \/etc\/sysctl.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># dnf install epel-release wireguard-tools\n# cd \/etc\/wireguard\n# wg genkey | tee private.key | wg pubkey &gt; public.key\n# touch wg0.conf\n# openssl rand -base64 32 &gt; preshared.key\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Below is my server configuration which has been sanitized. You will have to provide your specific keys. Put the config below in your <code>wg0.conf<\/code> 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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Interface]\nPrivateKey = &lt;Server Private Key&gt;\nAddress = fd00:f1ce:fd0d:1776::1\nAddress = 192.168.128.1\nListenPort = 51820\n\n&#91;Peer]\nPublicKey = &lt;Home Public Key&gt;\nPresharedKey = &lt;Your Preshared Key&gt;\nAllowedIPs = fd00:f1ce:fd0d:1776::2, 192.168.128.2\/32, 192.168.1.0\/24<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># firewall-cmd --permanent --zone=public --add-service=wireguard\n# firewall-cmd --permanent --zone=trusted --add-interface=wg0\n# firewall-cmd --permanent --zone=trusted --add-forward\n# firewall-cmd --permanent --zone=public --add-forward\n# firewall-cmd --reload<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first and most important step is to enable routing on the home endpoint.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># sysctl -w net.ipv4.ip_forward=1\n# sysctl -w net.ipv6.conf.all.forwarding=1\n# echo \"net.ipv4.ip_forward=1\" &gt;&gt; \/etc\/sysctl.conf\n# echo \"net.ipv6.conf.all.forwarding=1\" &gt;&gt;     \/etc\/sysctl.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install WireGuard<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># dnf install wireguard-tools\n# cd \/etc\/wireguard\n# wg genkey | tee private.key | wg pubkey &gt; public.key\n# touch wg0.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Configure WireGuard.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Interface]\nPrivateKey = &lt;Home Private Key&gt;\nAddress = fd00:f1ce:fd0d:1776::2\nAddress = 192.168.128.2\/32\n\n&#91;Peer]\nPublicKey = &lt;Server Public Key&gt;\nPresharedKey = &lt;Pre-shared Key&gt;\nAllowedIPs = fd00:f1ce:fd0d:1776::1, 192.168.128.1\/32\nEndpoint = 192.0.2.1:51820\nPersistentKeepalive = 25\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The firewall on the home side of the WireGuard tunnel is more complex because NAT must be enabled so that communication works bi-directionally.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># firewall-cmd --permanent --new-policy=wg-to-lan\n# firewall-cmd --permanent --zone=trusted --add-interface=wg0\n# firewall-cmd --permanent --zone=trusted --policy=wg-to-lan --add-ingress-zone=trusted\n# firewall-cmd --permanent --zone=trusted --policy=wg-to-lan --add-egress-zone=public\n# firewall-cmd --permanent --zone=trusted --policy=wg-to-lan --set-target ACCEPT\n# firewall-cmd --permanent --zone=trusted --add-forward\n# firewall-cmd --permanent --zone=public --add-masquerade\n# firewall-cmd --permanent --zone=public --add-forward\n# firewall-cmd --reload<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now we can bring the tunnel up. Do the following on both the VPS and the home sides.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># systemctl enable --now wg-quick@wg0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># sudo wg\ninterface: wg0\n  public key: &lt;Home Public Key&gt;\n  private key: (hidden)\n  listening port: 58512\n\npeer: &lt;VPS Public Key&gt;\n  preshared key: (hidden)\n  endpoint: 192.0.2.1:51820\n  allowed ips: fd00:f1ce:fd0d:1776::1\/128, 192.168.128.1\/32\n  latest handshake: 1 minute, 1 second ago\n  transfer: 49.61 MiB received, 438.12 MiB sent\n  persistent keepalive: every 25 seconds\n\n# ping -c 5 192.168.128.1\nPING 192.168.128.1 (192.168.128.1) 56(84) bytes of data.\n64 bytes from 192.168.128.1: icmp_seq=1 ttl=64 time=19.4 ms\n64 bytes from 192.168.128.1: icmp_seq=2 ttl=64 time=19.7 ms\n64 bytes from 192.168.128.1: icmp_seq=3 ttl=64 time=18.0 ms\n64 bytes from 192.168.128.1: icmp_seq=4 ttl=64 time=18.3 ms\n64 bytes from 192.168.128.1: icmp_seq=5 ttl=64 time=22.9 ms\n\n--- 192.168.128.1 ping statistics ---\n5 packets transmitted, 5 received, 0% packet loss, time 4006ms\nrtt min\/avg\/max\/mdev = 17.993\/19.646\/22.872\/1.737 ms<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On the VPS, do the same thing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># wg\ninterface: wg0\n  public key: &lt;VPS Public Key&gt;\n  private key: (hidden)\n  listening port: 51820\n\npeer: &lt;Home Public Key&gt;\n  preshared key: (hidden)\n  endpoint: &lt;Home IP&gt;:58512\n  allowed ips: fd00:f1ce:fd0d:1776::2\/128, 192.168.128.2\/32, 192.168.1.0\/24\n  latest handshake: 45 seconds ago\n  transfer: 2.27 GiB received, 259.69 MiB sent\n\n# ping -c 5 192.168.128.2\nPING 192.168.128.2 (192.168.128.2) 56(84) bytes of data.\n64 bytes from 192.168.128.2: icmp_seq=1 ttl=64 time=18.0 ms\n64 bytes from 192.168.128.2: icmp_seq=2 ttl=64 time=18.0 ms\n64 bytes from 192.168.128.2: icmp_seq=3 ttl=64 time=18.2 ms\n64 bytes from 192.168.128.2: icmp_seq=4 ttl=64 time=19.2 ms\n64 bytes from 192.168.128.2: icmp_seq=5 ttl=64 time=19.3 ms\n\n--- 192.168.128.2 ping statistics ---\n5 packets transmitted, 5 received, 0% packet loss, time 4005ms\nrtt min\/avg\/max\/mdev = 17.961\/18.527\/19.331\/0.603 ms<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># ping -c 5 192.168.1.1\nPING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.\n64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=22.0 ms\n64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=20.6 ms\n64 bytes from 192.168.1.1: icmp_seq=3 ttl=63 time=20.6 ms\n64 bytes from 192.168.1.1: icmp_seq=4 ttl=63 time=20.2 ms\n64 bytes from 192.168.1.1: icmp_seq=5 ttl=63 time=20.0 ms\n\n--- 192.168.1.1 ping statistics ---\n5 packets transmitted, 5 received, 0% packet loss, time 4006ms\nrtt min\/avg\/max\/mdev = 20.031\/20.694\/22.026\/0.707 ms<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># firewall-cmd --permanent --zone=public --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=192.168.128.2<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[4],"tags":[],"class_list":["post-551","post","type-post","status-publish","format-standard","hentry","category-information-technology"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/posts\/551","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/comments?post=551"}],"version-history":[{"count":0,"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/posts\/551\/revisions"}],"wp:attachment":[{"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/media?parent=551"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/categories?post=551"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/goblackcat.com\/wordpress\/wp-json\/wp\/v2\/tags?post=551"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}