Published Jul 27, 2026, 10:00 AM EDT Maker, meme-r, and unabashed geek, Joe has been writing about technology since starting his career in 2018 at KnowTechie. He's covered everything from Apple to apps and crowdfunding and loves getting to the bottom of complicated topics. In that time, he's also written for SlashGear and numerous corporate clients before finding his home at XDA in the spring of 2023. He was the kid who took apart every toy to see how it worked, even if it didn't exactly go back together afterward. That's given him a solid background for explaining how complex systems work together, and he promises he's gotten better at the putting things back together stage since then. My ISP took my public IPv4 address away years ago, and CGNAT has been the bane of my home lab ever since. Tailscale solved private access so well that I mostly stopped caring. But Tailscale deliberately doesn't do one thing: raw public ingress where my services see the real IP address of whoever's connecting. Funnel is HTTPS-only, and everything arriving through a relay or proxy logs as the tunnel's address, which means fail2ban and intrusion monitoring tools go blind, as does geo-block. So I built the missing piece myself with a WireGuard tunnel running on Oracle Cloud's free tier. An afternoon’s worth of work later and I had services able to log carrier IP addresses from behind CGNAT, thanks to a handy iptables trick. Oracle's free tier is good, once you know the setup traps That's two firewalls, one checkbox, and an MTU footgun Oracle's Always Free tier includes a small AMD VM (VM.Standard.E2.1.Micro) with a real public IPv4 address, forever, for $0. That address is the point of signing up, because the scarcity of IPv4 addresses is the reason CGNAT exists, and Oracle gives you one for free. I picked the little x86 shape over the more powerful Arm option deliberately, because the Ampere A1 instances are a capacity lottery in most regions, and a WireGuard relay doesn't need the cores. Upgrade the account to Pay As You Go anyway, then set a $1 budget. Upgrading to PAYG stops Oracle from reclaiming idle VMs, and the budget alert means you don't get any nasty surprises come the end of the month. The setup went (mostly) smoothly, and when it didn't, it was my fault. I skipped the Assign a public IPv4 address option during the instance creation, which was luckily recoverable because the VM landed in a public subnet. It's still only a few minutes to create a new VM, and most of that is waiting for Oracle to provision it. Then I met Oracle's double firewall. Yes, two. A cloud-side Security List, and a set of iptables rules baked into the Ubuntu VM that reject everything except SSH. Both need matching rules to enable an open port. The other issue is that the Ubuntu image ships with MTU 9000, and will silently break WireGuard unless you pin the tunnel to MTU 1420. Maybe I should have said issues, because the IPv6 firewall is empty. You don't need that if you're only using IPv4, but it's worth knowing if you plan on using IPv6. Every one is a five-minute fix once you know about them, or a lost evening and security hole if you don't. Oracle Cloud Oracle Cloud's free tier is a fantastic place to host home lab resources. Why DNAT without SNAT is the trick everyone skips Your firewall can finally see who's knocking The standard recipe for forwarding a port over WireGuard that you'll see in most tutorials works, but it's broken by design. Most of them add a masquerade rule pointing at the tunnel, which rewrites the source address of incoming traffic. Your home service then sees every incoming visitor as 10.66.66.1. This breaks ban lists; logging and abuse reports are useless. The masquerade wasn't even necessary. It papers over a routing problem that policy routing solves correctly, and I found the fix in a GitHub gist by pituluk. The server-side rewrites only the destination of incoming packets, pointing them down the tunnel while the real source address rides along untouched: # On the VPS, in wg0.conf — DNAT only, no SNAT toward the tunnelPostUp = iptables -t nat -A PREROUTING -p tcp -i ens3 --dport 25565 -j DNAT --to-destination 10.66.66.2PostUp = ip -4 rule add iif wg0 table main That's one half, but the clever part lives at home. Your reply traffic would normally exit through your CGNAT connection with the wrong source address, killing every connection, which is why tutorials reach for masquerade. Instead, set up the WireGuard client to route its reply traffic back through the tunnel. # On the home peer — the part everyone skipsTable = offPostUp = ip route add default via 10.66.66.1 dev wg0 table 123PostUp = ip rule add from 10.66.66.2 lookup 123 Everything else that machine does still uses the normal connection; no full-tunnel routing, no masquerade, no lost source addresses. And it works. I tested using my phone on a cellular connection at my VPS, and watched the logs on a container sitting behind CGNAT in my office. And the logs? They showed 172.225.30.89 - - [26/Jul/2026 09:41:12] "GET / HTTP/1.1" 200. I was expecting to see an IP address from my cellular carrier, and whois said it was Akamai Technologies instead. That’s because I had iCloud+ Private Relay running on my phone, which uses Akamai. After turning that off, I could see my T-Mobile block. I then added the masquerade rule back, and the next request came from the 10.66.66.1 IP address on the Oracle VM. As a bonus, the iPhone's page load came from two different Akamai addresses within a single second, because Private Relay assigns egress IPs per connection. If your ban logic assumes one visitor equals one IP, modern smartphones break this without you noticing. Adding new port forwards is a short process This is almost as easy as using a GUI Adding a new service later is a two-stop trip, and it's the same two stops every time. Oracle runs a firewall in the cloud (the Security List), and your VM runs another one on the box, and a port isn't open until both of them agree — this tripped me up more than once. Stop one: add an ingress rule to your Security List with source 0.0.0.0/0, protocol , and destination port . Stop two: duplicate the DNAT pair in your wg0.conf, swap in the new values, and restart the tunnel: PostUp = iptables -t nat -A PREROUTING -p -i --dport -j DNAT --to-destination PostDown = iptables -t nat -D PREROUTING -p -i --dport -j DNAT --to-destination After a quick restart of the service with sudo systemctl restart wg-quick@wg0, the port wass live. You might expect a third stop at the VM's INPUT chain, but forwarded traffic never touches it. DNAT happens before routing, so the packets ride the FORWARD chain straight into the tunnel. The INPUT rules only matter for ports the VPS itself answers on, like the WireGuard listener. CGNAT is a tax but the fix is now free The finished path runs from any client on the internet, through Oracle's free VM, down a WireGuard tunnel my container dialed outward (so my router config never changed), to services that log addresses. The detour through Oracle's Ashburn servers adds 8ms of round-trip time, which is nothing, and the tunnel survives reboots on both ends. Tailscale is still in my setup doing what it does best, and I'd still point anyone toward letting Tailscale do the scary part first for private access, because this build solves public ingress specifically. But this fixes projects that need a real IP address instead of a Tailnet one, and it bypasses CGNAT through a free service.
I bypassed CGNAT for free using Oracle's forever-free cloud tier, and it fixed the one thing Tailscale couldn't
Full Article
Original Source
Read the full article at Xda-developers →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.