Exposing my services behind CGNAT without Cloudflare

I describe my homemade solution when I need to expose my services behind a CGNAT, without using Cloudflare.

Exposing my services behind CGNAT without Cloudflare
Logo of SSH
💡
As usual, this is feedback that I am publishing here, firstly for my own purposes of documenting my work and secondly, why not, to inspire others. However, this is neither a miracle solution nor a comprehensive guide. I invite anyone reading this to take this article with a pinch of salt and to conduct their own research in order to create their own configuration that meets their own needs.
✏️
Updated on the 03rd February 2026

At home, on my Proxmox server, I host two VMs that serve this blog, my Nextcloud space, and a BorgBackup instance to host backups for a friend.

When everything is working properly, thanks to my fiber connection and my public IPv4 address, I can easily expose all of my services by linking my subdomains to my IP provided by my LiveBox:

However, when this fiber connection fails, I have to use a backup 4G connection, which does not offer me a direct public IPV4 address! My 4G connection shares a single public IPV4 address with a whole host of other 4G customers thanks to what is known as CGNAT. This mechanism does not allow me to expose my services, so I had to find a workaround.

The solution, which is simple, fast, and free, is to set up a Cloudflare tunnel. I install a client on my main server, entrust the management of my domains to Cloudflare, configure a tunnel, and I can once again expose my services.

Except that Cloudflare is lava 🌋 because it is on its way to becoming the leading proxy and web policeman, giving it unprecedented power to control global traffic: it can decide to authorize certain connections under the guise of protecting against DDOS attacks, filtering CSAM content, and blocking malware... But as usual, entrusting such a mission to a private actor means trading security for freedom: tomorrow, Cloudflare will become a mega web enforcer that will have the right to give you access to a particular online service only if you meet its criteria. It could cut off access to third-party browsers, smartphones it deems obsolete, certain operating systems, certain IP addresses or countries... in short, I don't want to feed this machine.

So it became clear to me that I had to do without such a solution. I wanted to learn how to configure my own proxy, on demand, without using such a provider: so no VPN or alternatives to Cloudflare such as Tailscale or others. I knew it was possible, particularly by using a VPS and configuring an SSH tunnel. But I didn't know anything about the subject and had only vaguely read a few comments on the internet. Until I took the bull by the horns, deleted my Cloudflare account, and did my research online. That's when I found the four resources that enabled me to achieve my goal:

  1. How to access a Linux server behind NAT via reverse SSH tunnel
  2. How to use a Reverse SSH tunnel to reach a server behind a NAT
  3. Roll your own Ngrok with Nginx, Letsencrypt, and SSH reverse tunnelling
  4. SSH Reverse Tunneling

Source No. 3 provided a comprehensive overview of what I wanted to do and reassured me that it was possible, as my current configuration corresponds exactly to the situation described in the article.

My main VM runs Caddy, configured to route requests to the right services based on subdomains. SSL certificates are managed by Caddy. So I already met the prerequisites for SSH reverse tunneling to work. All I had to do was:

  • Configure a VPS
    • I chose to use a Debian 12 droplet, hosted on a server in Germany via DigitalOcean.
    • I modified the configuration of the /etc/ssh/sshd_config to set GatewayPorts on yes
  • Redirect my domains to the public IP of the VPS
  • Open an SSH tunnel from my main VM
    • With the command line ssh -fN -R 443:localhost:443 root@[Public IPV4 of my VPS]

And as if by magic, just with the help of SSH, I could continue to use my HTTPS services by switching to my own proxy, without Cloudflare!

⚠️
Is it the best solution? I don't think so. Is it the simplest? Definitely not! Am I taking risks in terms of security? I need to continue researching the subject to really understand what I'm doing here. But is it a good solution for me when I have to switch to 4G temporarily to continue enjoying my services? Clearly, yes! And I love the fact that I can just use SSH and a VPS o/

Quick Debug

How to now if the SSH Tunnel is established:

hl0dwig@cloud:~$ sudo lsof -i -n | egrep '\<ssh\>'
sshd          898        root 6u  IPv4      6027      0t0  TCP *:ssh (LISTEN)
sshd          898        root 7u  IPv6      6037      0t0  TCP *:ssh (LISTEN)
sshd-sess 3502359        root 7u  IPv4 133797349      0t0  TCP 192.168.1.21:ssh->192.168.1.27:46906 (ESTABLISHED)
sshd-sess 3502543     hl0dwig 7u  IPv4 133797349      0t0  TCP 192.168.1.21:ssh->192.168.1.27:46906 (ESTABLISHED)
ssh       3503301     hl0dwig 3u  IPv4 133799514      0t0  TCP 192.168.1.21:48574->167.172.170.101:ssh (ESTABLISHED)
ssh       3503301     hl0dwig 4u  IPv6 133797846      0t0  TCP [::1]:38854->[::1]:https (ESTABLISHED)