Infrastructure

Exposing Proxmox services behind CGNAT: VPS gateway + WireGuard + socat

When your ISP has given you a 100.64.0.0/10 address and you can no longer open a single port. Five workarounds compared, why Cloudflare Tunnel is not a sovereign option, and a step-by-step guide to the VPS + WireGuard + socat setup with end-to-end encryption.

Exposing Proxmox services behind CGNAT: VPS gateway + WireGuard + socat
Contents (15 sections)
  1. Why CGNAT blocks everything
  2. Five workarounds
  3. 1. Cloudflare Tunnel
  4. 2. Reverse SSH tunnel
  5. 3. frp (Fast Reverse Proxy)
  6. 4. Site-to-site VPN, pfSense to pfSense
  7. 5. European VPS + WireGuard + socat (recommended)
  8. The final architecture in detail
  9. WireGuard configuration on the client side (Proxmox)
  10. WireGuard configuration on the VPS side
  11. socat relay on the VPS
  12. Public reverse proxy in front of socat
  13. Tunnel security
  14. Limits and pitfalls
  15. Conclusion

You have built a tidy Proxmox cluster at home and deployed Nextcloud, Vaultwarden, your mail server, your wiki and your Matomo. Then, when you go to open port 443 to make it all reachable from outside, you discover that your ISP has assigned you an IP address in the 100.64.0.0/10 block. That is CGNAT (Carrier-Grade NAT): your apparent public IP is shared with other customers, you no longer control address translation, and every attempt at port forwarding fails silently.

The bad news: this is increasingly common, especially with mobile operators (fixed 4G/5G, rural boxes) and low-cost alternative ISPs. The good news: there are at least five ways around this lock, and one of them keeps 100% of your traffic under your own control.

Diagram showing why CGNAT blocks port forwarding: several customers share the same public IP, and the ISP no longer routes incoming ports to subscribers
Under CGNAT, the public IP is shared between several subscribers. The ISP no longer routes incoming ports to your router.

Why CGNAT blocks everything #

In a standard internet setup, your router receives a unique public IP and performs NAT between your private LAN and the outside world. You can configure port forwarding 443 → 192.168.1.10:443 on your router and everything works. Under CGNAT, things are different: your router receives an IP from the private 100.64.0.0/10 block, and an upstream router at the ISP puts several hundred subscribers behind a single public IP. You have no access to that carrier router.

The result: your outgoing requests work normally (HTTP, SSH, outgoing WireGuard). But no incoming connection from the internet can reach your LAN: the ISP does not know which customer to send the TCP SYN packet on port 443 to.

Five workarounds #

1. Cloudflare Tunnel #

The simplest setup: install the cloudflared daemon on the internal host; it opens an outgoing tunnel to Cloudflare and exposes your services on a Cloudflare subdomain. No network configuration at all, working in 10 minutes.

2. Reverse SSH tunnel #

The quickest method: ssh -N -R 0.0.0.0:443:127.0.0.1:443 user@mon-vps. The VPS opens its port 443 and forwards all traffic through the SSH tunnel. Sovereign (it is your VPS) and simple to set up. The downside: fragile over long periods, noticeable added latency, and SSH is not optimised for high-throughput web traffic. Good for debugging or occasional admin access, bad for production.

3. frp (Fast Reverse Proxy) #

An open-source tool written in Go, with a server daemon on the VPS and a client on the home LAN. It exposes arbitrary TCP/UDP ports. Faster than SSH -R, simpler than WireGuard. Moderately sovereign (it depends on the upstream project), it remains a good option if you want to avoid the complexity of WireGuard.

4. Site-to-site VPN, pfSense to pfSense #

An enterprise multi-site approach: one pfSense at home, one pfSense on a VPS, with an IPsec or OpenVPN tunnel between them. Very robust, but it requires two dedicated pfSense machines and real networking expertise. Oversized for a single homelab, ideal for connecting several business sites.

The most balanced option: a VPS with a sovereign European hosting provider (Hetzner, OVH, Scaleway, Infomaniak) as the gateway, an encrypted WireGuard tunnel between the VPS and the Proxmox host, and a socat relay that forwards the VPS's public port 443 traffic to the internal services through the tunnel. End-to-end encryption, full sovereignty and native WireGuard performance.

The final architecture in detail #

Here is how packets travel in the recommended setup. An end client hits port 443 on the VPS, which goes through the public reverse proxy; socat then wraps the TCP stream in the encrypted WireGuard tunnel, which comes out on the Proxmox host behind the CGNAT, where a second socat (or the LXC service directly) receives the stream.

Complete network architecture: internet to VPS (port 443 + reverse proxy + socat), then an encrypted WireGuard tunnel crossing the CGNAT to the internal Proxmox host and the destination LXC container
The full flow: internet → VPS (Nginx + socat) → WireGuard tunnel → Proxmox host → destination LXC. End-to-end encryption, no port opened on the ISP side.

WireGuard configuration on the client side (Proxmox) #

We install WireGuard directly on the Proxmox host (or in a dedicated LXC if you prefer to isolate it). The configuration file looks like this.

INI
# /etc/wireguard/wg0.conf (Proxmox side, behind CGNAT)
[Interface]
PrivateKey = <cle_privee_proxmox>
Address = 10.66.66.2/32
MTU = 1280

[Peer]
PublicKey = <cle_publique_du_vps>
Endpoint = vps.exemple.fr:51820
AllowedIPs = 10.66.66.1/32
PersistentKeepalive = 25

The MTU = 1280 directive matters too: it avoids fragmentation of encapsulated UDP packets on mobile networks with a reduced MTU. It is the value WireGuard officially recommends for unreliable paths.

WireGuard configuration on the VPS side #

INI
# /etc/wireguard/wg0.conf (public VPS side)
[Interface]
PrivateKey = <cle_privee_vps>
Address = 10.66.66.1/32
ListenPort = 51820

[Peer]
PublicKey = <cle_publique_du_proxmox>
AllowedIPs = 10.66.66.2/32
Bash
# Enable at boot on both sides
systemctl enable --now wg-quick@wg0

# Check that the tunnel is up
wg show wg0
# latest handshake must be < 2 minutes

socat relay on the VPS #

Once the WireGuard tunnel is up, we configure socat on the VPS to relay TCP traffic from the public port to the Proxmox host's internal IP inside the tunnel. We wrap it in a systemd unit so that the service can be restarted cleanly.

INI
# /etc/systemd/system/socat-nextcloud.service
[Unit]
Description=socat relay 443 vers Nextcloud via WireGuard
After=network-online.target wg-quick@wg0.service
Requires=wg-quick@wg0.service

[Service]
Type=simple
ExecStart=/usr/bin/socat -d -d TCP4-LISTEN:8443,reuseaddr,fork TCP4:10.66.66.2:443
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
Bash
systemctl daemon-reload
systemctl enable --now socat-nextcloud.service
systemctl status socat-nextcloud.service

Public reverse proxy in front of socat #

To serve several services on the same public port 443, we put Nginx (or Traefik) in front. Each vhost routes to a different socat according to the server_name.

Nginx
# /etc/nginx/sites-enabled/nextcloud.conf (on the VPS)
server {
    listen 443 ssl http2;
    server_name cloud.exemple.fr;

    ssl_certificate     /etc/letsencrypt/live/cloud.exemple.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cloud.exemple.fr/privkey.pem;

    client_max_body_size 10G;
    proxy_request_buffering off;

    location / {
        proxy_pass http://127.0.0.1:8443;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Tunnel security #

  • The WireGuard keys are encrypted on the Proxmox disk with LUKS if the host is physically exposed.
  • The VPS firewall only allows port 51820 UDP (WireGuard) and 443 TCP (public reverse proxy). Admin SSH is restricted by source IP.
  • The WireGuard tunnel is itself encrypted (ChaCha20-Poly1305). The socat traffic inside is in clear text within the tunnel but already encrypted by TLS at the application level (Nginx terminates TLS for the client, and the internal service runs its own TLS if needed).
  • For strict end-to-end encryption with no TLS termination at all on the VPS, use an Nginx stream { ssl_preread; } block with SNI routing instead of a standard HTTP vhost.

Limits and pitfalls #

  • Extra latency: 5 to 30 ms depending on how far away the VPS is. Place it in the same region as your users.
  • Bandwidth is limited by the VPS link: a 1 Gbps VPS is more than enough, but check it against your usage.
  • If the VPS goes down, every exposed service becomes unreachable. Plan external monitoring (UptimeRobot, or a second standby VPS in DNS round-robin).
  • An entry-level VPS costs around €5 to €10 a month with sovereign European hosting providers. Include it in the project's total cost of ownership.
  • UDP services (games, VoIP) need a different socat or Nginx stream configuration: think carefully about the protocol being carried before adding a service.

Conclusion #

CGNAT is not a dead end. With a European VPS at a few euros a month, WireGuard and socat (all three open source and mature), you can expose your Proxmox services publicly with the same performance as a direct fixed IP, while keeping 100% of your traffic under your own control. It is more work than a Cloudflare tunnel, but it is consistent with the whole approach: if you are leaving the big platforms, you do not let them back in through the cellar.

To go further with the infrastructure behind this tunnel, the PBS guide explains how to protect the whole fleet with an enterprise-grade backup policy, and the Proxmox vs TrueNAS vs Unraid comparison helps you choose the right base if you are starting from scratch.

About
01/Read nextThree articles

Related articles

Infrastructure

Exploit foundries: your firewall has become the front line

Two undergraduates in Changsha ran an automated pipeline that decrypts firmware, decompiles it through thousands of calls, forms hypotheses about vulnerabilities, writes the exploit and tests it in a lab, in a loop, day and night. More than a dozen unknown vulnerabilities in a month on network devices, some fifty organisations targeted, thirteen collection agents on a scheduled task. What this means for an SME's inventory.

cybersecurityvulnerability
Infrastructure

Intermittent SERVFAIL: the orphaned DS record that broke 15% of lookups

A CI pipeline failing intermittently, two different error messages, an obvious suspect that had nothing to do with it, and an invisible cause: a DNSSEC record still published at the parent while the zone was no longer signed. Post-mortem of a nasty DNS failure, with the method that finally settled it.

dnssecdns
Infrastructure

Patching 36 servers without unattended-upgrades

There are three doctrines: do nothing, automate everything blindly, or look first and then decide. We removed unattended-upgrades from the whole fleet and dismantled our own automatic patching with Ansible, in favour of a “visibility first” doctrine backed by PatchMon. Why, how, and the routine that goes with it.

patch managementpatchmon

What next

Infrastructure that is managed and monitored day and night?

Bastion, SIEM, patches applied on purpose, tested backups: what we apply for our clients. First conversation free.

Discuss your project

Reply within 24 working hours, from one of the two founders. Or by phone: +33 7 59 96 14 99.