← Back to Wiki
Networking / Self-Hosting

Large Downloads Fail Only When You're Away From Home: Cloudflare's 100 MB Proxied Response Cap

Downloading a movie from a self-hosted Jellyfin server worked perfectly at home and failed on every other network. Streaming was fine in both places. Nothing in Jellyfin's logs looked wrong. The cause was a documented Cloudflare limit most self-hosters never hit, because most self-hosted traffic is small. The shape of the failure makes it look like an application bug for as long as you are willing to believe that.

Share on X

The limit

Cloudflare's Free plan caps the body of a single proxied response at 100 MB. That is per response, not per month, and it applies to anything going through an orange-clouded DNS record. Ask a proxied origin for a 40 GB file and Cloudflare terminates the connection almost immediately. It does not stream 100 MB and stop politely.

This bites self-hosters with:

Why it looks like an app bug

Two things disguise it. Both are worth internalising, because they generalise to any CDN-in-front problem:

It works at home. Mobile apps like Jellyfin's find the server by LAN discovery or a direct IP when you are on the same network. They never touch the public hostname and never traverse Cloudflare. The proxied path is only exercised when you are away, which makes it look like a client, ISP or mobile-data problem rather than infrastructure.

Streaming keeps working. Transcoded and direct-play streaming arrives as many small segments, each far below the cap. Only the single-response full-file download crosses it. "Playback is fine, downloads are broken" is a genuinely confusing signal, and it points away from the network.

App at home LAN discovery direct, never touches the CDN ✓ App elsewhere public hostname Cloudflare proxy 100 MB per response segments ✓ full file ✗ Reverse proxy + media server
Why it only fails away from home. The same app, the same server. Only the off-LAN path crosses the proxy, and only the full-file response crosses the cap.

Confirming it, rather than guessing

Read the reverse proxy's access log at the origin. Caddy here, though the same fields exist in nginx or Traefik. You want three things together:

  1. A request to the download endpoint. /Items/<id>/Download for Jellyfin.
  2. Cloudflare headers on the request. Cf-Ray and Cdn-Loop: cloudflare. Those prove the request came through the proxy and not directly, which is the fact the whole diagnosis turns on.
  3. A duration in tens of milliseconds with an error like writing: http2: stream closed. A connection that dies that fast on a multi-gigabyte file never got going. That is not a slow network or a timeout.
journalctl -u caddy -f | grep -i download
# or, if you log to a file:
tail -f /var/log/caddy/access.log | grep Download

One detail rules out the obvious workaround. Clients retry with a Range header to resume, and those retries die just as fast. Cloudflare rejects on the advertised remaining length, not on bytes accumulated so far. So "just resume it" cannot get you past a file over the cap, and chunked client-side downloading does not rescue it either.

The fix: stop proxying that one hostname

The cap is not a setting you can raise on any economically sane plan. Take that single hostname off Cloudflare's proxy and leave the rest of your domain alone.

In the Cloudflare dashboard, set that record to DNS only, the grey cloud, instead of Proxied, the orange one. What you give up on that hostname is real and worth stating. Origin IP hiding, Cloudflare's DDoS absorption, its WAF. What you get back is unlimited response sizes.

If hiding your home IP was the reason for proxying, point the DNS-only record at a cheap VPS and forward the traffic. A raw TCP passthrough on 80 and 443 to your reverse proxy at home keeps the origin hidden with no CDN in the response path. Plan for two things if you do:

Prevention

The one-line version: Cloudflare's Free plan will not proxy a response body over 100 MB. Streaming survives because segments are small. Full-file downloads do not. Set that one hostname to DNS-only, and if you need the origin hidden, forward through a small VPS instead of a CDN.