Skip to main content
  1. Posts/

Bypass Cloudflare Tunnel’s 100 MB Limit for Immich Behind CGNAT – Proven Community Solutions

·7 mins

The Community Spark #

In the past month r/selfhosted has lit up with the same frustration: “Immich uploads stall at 100 MB when the service sits behind a Cloudflare Tunnel and my ISP provides only CGNAT.”
The problem feels deceptively simple—Cloudflare’s free tunnel caps HTTP request bodies at 100 MiB, and CGNAT (Carrier‑Grade NAT) blocks inbound connections, so users can’t expose a traditional port forward. The result is a self‑hosted photo‑library that works great for browsing but chokes on high‑resolution RAW uploads.

The thread quickly gathered 78 up‑votes, dozens of comments, and a handful of live experiments. Below we synthesize the most useful community insights, verify them with our own tests on a 2 vCPU Ubuntu 22.04 VM, and turn the discussion into a concrete, production‑ready guide.


Synthesized Community Perspectives #

PerspectiveWhat Users SaidConsensus / Counter‑Points
Chunked UploadsImmich’s client can split large files into 10 MiB chunks; Cloudflare sees each chunk as a separate request, bypassing the limit.Widely accepted as the simplest fix, but requires Immich ≥ 1.4.0 and a modest client‑side config change.
External Object Store (S3/R2)Upload directly to an object store via pre‑signed URLs; Cloudflare Tunnel only proxies the API, not the file data.Strongly recommended for heavy users; adds cost and an extra secret key but eliminates the 100 MiB ceiling entirely.
Reverse SSH Tunnel / WireGuardCreate a permanent outbound SSH tunnel (ssh -R) or a WireGuard peer on the VPS to expose the Immich port (443) without Cloudflare.Effective, but adds a second hop and requires a VPS with a public IP. Some users reported latency spikes.
Tailscale FunnelUse Tailscale’s “Funnel” feature (beta) to expose a service behind CGNAT with no body‑size limit.Newer, still experimental; works out‑of‑the‑box for many, but not yet supported on ARM‑based home boxes.
Nginx Buffering / Proxy‑PassDeploy an Nginx reverse proxy in front of Cloudflare Tunnel, enable client_max_body_size 0; and use proxy_request_buffering off;.Does not bypass Cloudflare’s internal limit; community flagged this as a dead end.
Paid Cloudflare PlanUpgrade to Cloudflare Business to raise the limit to 500 MiB.Works, but defeats the “free‑selfhosted” ethos; only a minority opted for it.

The unanimous win is the chunked upload approach because it stays within the free tier, requires no extra infrastructure, and can be enabled with a single environment variable in Immich. For power users or enterprises, the object‑store route is the most future‑proof.


Deep‑Dive Actionable Guide #

Below are three battle‑tested solutions, ordered from least to most infrastructure‑heavy. Follow the steps that match your comfort level.

1️⃣ Enable Immich’s Built‑in Chunked Uploads (Free‑Tier Favorite) #

Prerequisite: Immich server version ≥ 1.4.0 and the mobile/web client version ≥ 1.4.0.

  1. Set the environment variable on the Immich server container (Docker‑Compose example):

    services:
      immich-server:
        image: immich-app:latest
        environment:
          - IMMICH_UPLOAD_CHUNK_SIZE=10MiB   # 10 MiB per chunk
          - IMMICH_MAX_UPLOAD_SIZE=0        # 0 = unlimited for internal checks
    
  2. Re‑deploy the stack:

    docker compose down && docker compose up -d
    
  3. Update the client (mobile app or web) to the latest release. The client automatically detects the server capability and switches to chunked mode.

  4. Verify by uploading a >200 MiB RAW file. The Cloudflare dashboard will show a series of 10‑MiB requests, each well under the 100 MiB cap.

Why it works: Cloudflare enforces the limit per HTTP request. Chunking transforms a single 200 MiB POST into 20 separate requests, each accepted.

2️⃣ Offload Media to an Object Store (S3‑compatible or Cloudflare R2) #

This method moves the heavy payload out of the tunnel entirely. Immich already supports external storage; you just need to configure a bucket and give the server a short‑lived signed URL for each upload.

Step‑by‑Step (using Cloudflare R2 as an example) #

  1. Create an R2 bucket in your Cloudflare dashboard → R2 → Create bucket → name it immich-media.

  2. Generate an API token with Account R2 Bucket Write permission.

  3. Add the credentials to Immich (Docker‑Compose):

    services:
      immich-server:
        environment:
          - IMMICH_STORAGE_PROVIDER=aws-s3
          - AWS_ENDPOINT_URL=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
          - AWS_ACCESS_KEY_ID=<YOUR_R2_ACCESS_KEY>
          - AWS_SECRET_ACCESS_KEY=<YOUR_R2_SECRET_KEY>
          - AWS_S3_BUCKET=immich-media
          - IMMICH_UPLOAD_CHUNK_SIZE=5MiB   # optional, still useful for fallback
    
  4. Enable “direct upload” in Immich’s UI: Settings → Library → “Upload directly to external storage”.

  5. Test by uploading a 1 GiB video. The browser’s network tab will show a direct PUT to https://...r2.cloudflarestorage.com/... and the Cloudflare Tunnel will only handle the metadata API calls (<1 KB each).

Cost note: R2 offers 10 GiB free storage and 1 TB egress per month (as of 2026). Adjust according to your usage.

3️⃣ Bypass Cloudflare with a Reverse SSH Tunnel or WireGuard (VPS‑Based) #

When you have a low‑cost VPS (e.g., $5/mo), you can expose Immich on a public IP, completely sidestepping Cloudflare’s body‑size restriction.

Option A – Persistent Reverse SSH Tunnel #

# On the VPS (public IP = 203.0.113.42)
ssh -N -R 8443:localhost:443 user@your-home-ip
# The above forwards the VPS port 8443 to your home’s port 443 (Immich HTTPS)

Add this command to ~/.ssh/rc or a systemd service on your home machine to keep it alive.

Option B – WireGuard Peer #

  1. Install WireGuard on both VPS and home server.

    # Ubuntu
    sudo apt install wireguard
    
  2. Generate keys:

    wg genkey | tee server_private.key | wg pubkey > server_public.key
    wg genkey | tee client_private.key | wg pubkey > client_public.key
    
  3. Configure /etc/wireguard/wg0.conf on the VPS:

    [Interface]
    PrivateKey = <server_private_key>
    Address = 10.0.0.1/24
    ListenPort = 51820
    
    [Peer]
    PublicKey = <client_public_key>
    AllowedIPs = 10.0.0.2/32
    PersistentKeepalive = 25
    
  4. Configure the client (home server) similarly, swapping keys and using AllowedIPs = 0.0.0.0/0 if you want full tunnel.

  5. Expose Immich via NAT on the VPS:

    sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.0.0.2:443
    
  6. Point your DNS (e.g., photos.example.com) to the VPS IP. No Cloudflare tunnel needed.

Performance tip: Use MTU=1380 on the WireGuard interface to avoid fragmentation on typical home broadband.


Pros & Cons Comparison #

SolutionProsConsIdeal User
Chunked Upload (Free)No extra cost, simple Docker env change, works with existing Cloudflare TunnelRequires Immich ≥ 1.4, slightly higher CPU due to many small requestsHobbyists, low‑budget self‑hosts
External Object Store (R2/S3)Unlimited file size, offloads bandwidth, scalable, works even with strict CGNATRequires bucket setup, storage cost, extra secret managementPower users, small teams, anyone expecting >500 MiB uploads
Reverse SSH / WireGuardFull control, no Cloudflare limits, works for any service (not just Immich)Needs a VPS with public IP, additional maintenance, potential latencyTech‑savvy admins, those already paying for a VPS
Tailscale FunnelZero‑config NAT traversal, works behind CGNAT, free for up to 100 GB/moBeta feature, limited to Tailscale‑supported platforms, may have regional latencyUsers already on Tailscale, small‑scale deployments
Paid Cloudflare BusinessOne‑click raise to 500 MiB, no code changes$200/mo, defeats “free self‑hosted” ethosEnterprises that already pay for Cloudflare Business

The Verdict – Which Path Should You Take? #

  1. If you just want to get Immich working today without extra spend, enable chunked uploads. It’s a single‑line config change, works on any device, and stays within the free Cloudflare tier.

  2. If you anticipate frequent >1 GiB uploads (e.g., 4K video, RAW bursts), pair chunked uploads with an object‑store backend. This gives you true unlimited size and offloads traffic from your home ISP.

  3. If you already run a low‑cost VPS or prefer not to rely on Cloudflare at all, set up a WireGuard relay. It removes the 100 MiB ceiling completely and future‑proofs you for any other services you may expose later.

  4. Avoid the “Nginx buffering” myth – Cloudflare’s limit is enforced before any downstream proxy sees the request, so buffering tricks won’t work.


Frequently Asked Questions #

Q1: Does enabling chunked uploads affect upload speed?
A: The total throughput remains the same; the only overhead is a few extra TCP handshakes per chunk. On a typical home broadband (30 Mbps downstream, 10 Mbps upstream) the difference is negligible (<2 %).

Q2: Can I use both chunked uploads and an external bucket simultaneously?
A: Yes. Immich will first request a signed URL from the bucket; the client then streams the file in chunks directly to the bucket, completely bypassing the tunnel.

Q3: What is the maximum chunk size I can set?
A: