GUIDE, WITHOUT THE GUESSWORK

Reaching Your Self-Hosted Apps Behind CGNAT — Without Exposing Port 443

CGNAT removes inbound ports, not your options. A decision tree for clean domains and private access to Jellyfin and Immich using VPS relays, Cloudflare Tunnel, Tailscale, and WireGuard.

You bought a domain, pointed it at your home server, opened the port — and nothing. Welcome to CGNAT. Your ISP has put you behind a shared, carrier-grade NAT, so there is no public inbound port to forward. Some ISPs will open a single random port on request, which is how people end up sharing https://example.com:43117/ and hating it.

The good news: you can get clean, secure access to Jellyfin, Immich, and the rest without a public IP and without exposing port 443 on your home connection. The trick is to stop trying to accept inbound connections at home, and instead reach out to something that can accept them.

First, confirm you're actually behind CGNAT

Check the WAN IP your router shows, then compare it to your public IP (search "what is my IP"). If they differ — and especially if your router's WAN address is in 100.64.0.0/10 — you're behind CGNAT. Port forwarding will never work from the outside, no matter how the router is configured. That single check saves hours of debugging firewall rules that were never the problem.

The core idea: relay, don't expose

Every workable option follows the same shape: a machine with a real public IP accepts connections, and your home server maintains an outbound tunnel to it. Inbound at home stays fully closed. The options differ in who runs that public machine and how private the traffic stays.

Option 1 — Your own VPS as a reverse-proxy relay (most control)

Rent a small VPS, connect it to your home server over WireGuard, and run a reverse proxy (Caddy, Traefik, or nginx) on the VPS. The proxy terminates TLS for media.example.com and forwards over the WireGuard tunnel to Jellyfin/Immich at home.

Option 2 — Cloudflare Tunnel (fastest to set up, with a caveat)

cloudflared makes an outbound tunnel to Cloudflare's edge; you get a clean domain and TLS with almost no config and no VPS to manage.

The caveat that keeps coming up in self-host threads: Cloudflare terminates TLS at their edge, so your traffic is decrypted there, and Cloudflare's terms restrict heavy media streaming through the free proxy. For dashboards, photos, and light use it's excellent. For a privacy-sensitive Immich library or heavy Jellyfin streaming, many people are uncomfortable routing plaintext media through a third party — which points back to Option 1 or 3.

Option 3 — Tailscale (and Serve/Funnel) for private and family access

If access is mostly for you and a few people, a WireGuard-based mesh like Tailscale is often the cleanest answer: install it on the server and on each client, and the apps are reachable over the private tailnet with no public exposure at all.

A recurring gotcha: a Jellyfin/Docker setup works on one tailnet but breaks when shared across another tailnet. Cross-tailnet sharing has its own ACL and DNS rules — if a shared user can't connect, check the share's ACLs and that MagicDNS resolves the target on their side before assuming the container is misconfigured.

Option 4 — Keep it VPN-only (most private)

If nobody outside your household needs access, the simplest secure answer is to expose nothing. Run WireGuard (or Tailscale) and require clients to be on the VPN to reach any service. No public domain, no edge, no third party in the path. The cost is that every device must run a VPN client — fine for you, sometimes a hard sell for non-technical family members.

A decision tree

Protect the private media traffic

The whole reason CGNAT workarounds get debated so heavily is that media is sensitive. Two rules keep it clean:

  1. Don't let an unencrypted hop touch your library. If you use an external edge that terminates TLS, assume that operator can see the content. For private photos and home video, prefer a path where TLS terminates on hardware you control (your VPS or your own device) — Options 1, 3, and 4 all qualify.
  2. Separate "clean URL" from "who can reach it." A nice domain is about ergonomics; access control is a separate decision. Even with a public domain, gate sensitive apps behind auth (an auth proxy, or the app's own SSO) rather than relying on obscurity.

Recommended setups for common cases

The takeaway

CGNAT removes inbound ports, not your options. Stop trying to accept connections at home and instead maintain an outbound tunnel to something that can — a VPS you control, a tunnel provider, or a WireGuard mesh. Pick based on who needs access and how private the traffic must be, terminate TLS somewhere you trust, and you'll get clean https://media.example.com URLs with port 443 at home staying firmly shut.

From across the StoicSoft network

Hand-curated reads on the same topic from sister sites in the StoicSoft family.