David Gil Pérez · 22 October 2026

My app disappears every time LaLiga plays

Every time LaLiga plays, ZetaRead disappears for a big chunk of Spain. Kids open the app on a Sunday afternoon to read a story and get a blank screen, without a single error message.

The reason is a court order (AS) that lets LaLiga have Movistar, Orange, Vodafone, DIGI and Masmovil block, during matches, the IPs it says carry pirated football. A lot of those IPs are Cloudflare's, and Cloudflare serves thousands of sites from the same few addresses. One of them is ours. We have nothing to do with football, we just share an IP with someone who does.

I found out the embarrassing way: from hayahora.futbol, a site that tracks the blocks, not from our monitoring. Our uptime checker runs on a Telefónica line, so it went dark exactly when our users did. Here is what the last two months looked like:

When ZetaRead was blocked in Spain One column per day from late August to late October 2026, one row per hour of the day in Madrid time. Darker cells mean more of the five big Spanish ISPs were blocking ZetaRead in that hour. Blocks cluster on weekend afternoons and evenings, when LaLiga plays. 22 of 59 days had a block. 00h 06h 12h 18h 24h August September October weekends ISPs blocking 0 1 2 3 4 5
Every cell is one hour of one day, in Madrid time. Coloured cells are hours when ZetaRead was blocked on at least one of the five big ISPs; the darker, the more of them. 22 of 59 days had a block, almost always on weekend afternoons and evenings. Data: hayahora.futbol.

The usual advice doesn't fit

Before building anything we went through the obvious options. None of them works for a site that lives entirely on Cloudflare Workers:

  1. Ask Cloudflare for other IPs. We opened a ticket. Support's answer: a zone's IPs are not reassigned on any plan, paying or not. The only way to pick your addresses is bringing your own IP range, which is Enterprise-only. Not an option for a small product.
  2. Create a new zone. We tried a few, some in brand new accounts. All of them landed in the same 188.114.x range that gets blocked every match day.
  3. Turn the proxy off during matches. This is what sites with their own server do: switch the record to DNS only, which per Cloudflare's docs answers with the origin's real IP, so traffic skips Cloudflare's shared addresses. ZetaRead has no origin. The app is a Worker with D1 and R2, and it only exists inside Cloudflare, so there is nothing to point DNS at.
  4. Move to another CDN. The hayahora history has, next to 3,679 Cloudflare IPs, blocked addresses from Vercel, CloudFront, Fastly, BunnyCDN and OVH. Moving means rewriting the stack to end up on a different list.

A spare zone with different IPs

What did work: we already had a second Cloudflare zone, older, whose IPs had never shown up on any blocklist. With Cloudflare for SaaS it can serve other domains' hostnames, so our public names are registered there in advance and sit idle. When a block starts, we swap the main Worker's Custom Domains for a CNAME to that zone. Browsers land on IPs nobody is blocking, and a tiny Worker there forwards every request, signed, back to the main Worker through an internal hostname. That hop never leaves Cloudflare, where nothing is blocked.

How the spare zone works A browser in Spain goes through its ISP to the zone's IPs and the main Worker's Custom Domain. During a block the ISP drops those IPs. The guard, a cron Worker, reads the hayahora lists and calls the Cloudflare API to replace the Custom Domain with a CNAME to the spare zone. Requests then reach the spare zone's IPs, where a proxy Worker forwards them, signed, to the main Worker through an internal hostname. After 30 minutes without a block the guard swaps DNS back. hayahorablocked IPs per ISP guardcron Worker · 2 min API DNS · zetaread.comCustom Domain → main Worker (zone IPs) browserin Spain ISPMovistar, DIGI… blocks zone IPs zone IPsshared, blocked Custom Domainzetaread.com spare zone IPsanother zone, free proxy Workercustom hostname signed hop, inside Cloudflare main Workerchecks signature
Requests go to the zone's IPs and the main Worker's Custom Domain.

The main Worker checks the signature and handles the request as if it had arrived normally, real visitor IP included, so cookies, redirects and rate limits just work. Without a valid signature the internal hostname answers 404. It is not a second copy of the site for anyone to find.

The first time we used it, we flipped DNS by hand, hours into a block. The switch itself took a minute. The hours before it were the painful part, so the next step was obvious.

Let a Worker do it

A cron Worker reads the blocklists every couple of minutes. If one of our IPs is blocked on any ISP, and the spare zone answers and isn't blocked itself, it switches. It switches back after half an hour of quiet, because blocks come and go during a match. In between it keeps the spare zone's hostnames and certificates alive, and it emails us whenever it does something or can't. We rehearsed the whole loop outside a match and the site never stopped answering. The first real match is still ahead, so I'll believe it when I see it.

Things that bit us

Spain and the cron don't see the same IPs. Cloudflare answers based on where the DNS resolver is, so the cron, running somewhere else, resolves our name to different addresses than a Spanish user does. A guard watching only what it resolves would never notice the block. So the IPs Spain sees are configured by hand, checked from a Spanish line.

One forgotten header. The main Worker rewrote the URL but kept the internal hostname in the Host header. OpenNext builds URLs from Host, so its own internal rewrites looked external and the Worker started fetching itself over the internet. On Next 15 that quietly turned our 404 page into a 200. On Next 16 those requests just failed, which would have killed the whole thing at the next match. I caught it testing locally, when a request that should never have left my laptop came back from production. One-line fix, a lot of luck.

Idle hostnames expire. Cloudflare deletes a custom hostname a week after the domain stops pointing at it, and the certificate records change on every renewal. The guard keeps both fresh between blocks.

Don't deploy during a block. A deploy tries to recreate the Custom Domains on names that now have a CNAME, and fails. The guard's email says so, in capitals in my head.

This shouldn't be necessary

The guard only learns about a block when the blocklist does, so there are still a few bad minutes each time. If LaLiga ever reports the spare zone's IPs, we are back to square one. And honestly, I think blocking shared IPs to fight pirated streams is the wrong tool: it takes down banking apps and kids' reading apps to annoy pirates who just move to the next IP. Cloudflare and RootedCON have both taken it to the Constitutional Court (Xataka, Computerworld), and the Spanish startup association collects affected cases at asociacionstartups.es/laligagate. We sent ours.

The code

Everything above is open source under MIT: cf-zone-failover. Same code ZetaRead runs, minus anything of ours: the signed proxy, the origin side and the guard, with alerts in English or Spanish. The README explains the setup and the limits.