A CDN terminates your TLS, sits in the request path of every visitor, and sees every asset request your pages make. Most engineering teams choose one on price and latency. For a privacy company, those are the second questions — the first is jurisdictional: who operates the box that decrypts every request, and which courts can compel them?
We went through that decision for ciphera.net and our product frontends, picked Bunny, and have now run it in production long enough to have opinions with evidence. This is the checklist we used, what the setup actually looks like, and four things running it taught us — including one embarrassing number worth reading about so you don't repeat it.
Updated 18 October 2026: our websites moved onto Bunny Magic Containers between 15 August and 12 September 2026, and the setup section now says what that changes.
Update, 19-07-2026 — rewritten around our current production setup, with all network and pricing figures re-verified against bunny.net on this date.
Why a CDN Is a Privacy Decision
The CDN's entire job is to stand between your users and your servers. That means it observes every request — URL, IP, headers — for every visitor, on every page. You can architect so it holds nothing sensitive (more on that below), but you cannot architect away the fact that it sees traffic. Whoever operates it is inside your trust boundary whether you like it or not.
The asymmetry is easy to miss because static assets feel harmless. An icon request is just an icon request — until you notice that every one arrives with an IP address, a user agent, and a referer header naming the exact page being read. Aggregate those across a browsing session and a CDN's logs reconstruct visitor behavior about as well as an analytics script would. A company that won't run invasive analytics on its own pages has no business ignoring the same data accumulating in a vendor's log pipeline.
That makes jurisdiction the first filter. A US-headquartered CDN operates under the CLOUD Act wherever its edge nodes physically sit — the compulsion follows the company, not the server. Our vendor policy is explicit and ranked: residency, then privacy, then security, then robustness. Applied here, that ruled out the default answers (Cloudflare, CloudFront, Fastly) before performance was even discussed — not because they're bad at CDN-ing, but because they move the request path into a legal system we're deliberately building away from. Ciphera is a Belgian company; our data is stored in Switzerland; the CDN needed to be run by a European company.
The Checklist We Used
Five criteria, in the order we applied them:
- EU jurisdiction. Bunny is Slovenian, so the edge is operated by an EU company under GDPR. Its network is global (119 PoPs, see the last item), so the jurisdiction we rely on is the operator's, and the nodes sit wherever Bunny places them.
- Logging you can live with. Bunny's GDPR commitments include anonymizing personally identifiable data in logging, with a signed DPA available in the panel. "We anonymize" from an EU company under GDPR enforcement is a materially different promise than the same words from elsewhere.
- Content-agnostic serving. The CDN must do its job without interpreting the bytes. For our asset zones we treat the CDN as untrusted infrastructure on principle: it caches and moves opaque objects, whether those are PNGs or encrypted blobs. Any CDN feature that requires inspecting or transforming content gets extra scrutiny before we enable it. The websites that now run on Bunny's platform are the exception, as the setup section below explains.
- Transparent pricing. $0.01/GB for European and North American traffic on the standard network, volume tiers from $0.005/GB, and a $1 monthly minimum — all public on the pricing page, no sales call required. (All figures re-checked 19-07-2026.)
- Performance, last. 119 PoPs, 250+ Tbps network capacity, ~24 ms average global latency (bunny.net/network). Comfortably more than a content site needs — but it was the fifth question, not the first.
The order is the point. Plenty of CDNs pass criterion five. Very few pass one and two first.
What Our Setup Actually Looks Like
The architecture is deliberately boring:
- Origin: all static assets — product illustrations, icons, blog OG cards — live in Bunny Edge Storage, and every upload is also written to object storage in Switzerland, so Bunny never holds the only copy.
- Edge: Bunny pull zones (fifteen of them on 19 July 2026, across our sites and services) front that storage at
cdn.ciphera.net. The copy in Switzerland holds every file, so swapping CDN providers for these asset zones would mean pointing the new CDN at it, not a migration. The websites are different: ciphera.net, docs, help and status moved onto Bunny Magic Containers (about 42 regions) between 15 August and 12 September 2026, and the Pulse tracker and API are served through Bunny's edge, where TLS ends. Moving the websites would be a rebuild. - Assets are path-keyed and immutable. Every object uploads with
Cache-Control: public, max-age=31536000, immutableand is never edited in place — a changed image gets a new path. Edges can cache for a year with zero staleness risk, and there's no revalidation traffic to leak anything about visitors. - Assets are pre-sized at upload. No on-the-fly transformation service sits in the pipeline resizing images per request — what's in storage is exactly what's served.
Belgian company, Swiss storage, EU-operated edge. Each layer's jurisdiction is chosen, not inherited from a default.
Four Things Running It Taught Us
1. Cache keys will collapse things you didn't expect. Next.js ships an image optimizer that serves every image through query-string URLs (/_next/image?url=…&w=…&q=…). Our Bunny zone's cache configuration collapsed query strings — so one cached body was served for every image on the site. The fix is right in our next.config.ts, comment and all: images are served directly from cdn.ciphera.net, path-keyed and pre-sized, unoptimized: true, never through the optimizer. The general lesson: know exactly what your CDN includes in its cache key before you put a URL scheme that depends on query strings behind it.
2. Measure your real cache-hit rate — ours was embarrassing. When we actually audited it in March, our aggregate cache-hit rate was 8.65%. Not 86 — eight point six five. The immutable asset zones were fine; the damage came from zones and paths that were never being cached at all, silently proxying every request to origin. A CDN with a single-digit hit rate is an expensive DNS alias. We fixed the worst offenders the same day with explicit cache rules. Watch the hit rate per zone, too, not just the fleet aggregate — one silently bypassing zone hides comfortably behind nine healthy ones in an average. If you haven't looked at your numbers this quarter, they are probably lower than you think.
3. Push security rules to the edge. Internal paths — metrics endpoints, health checks, anything under /internal/ — shouldn't rely on application middleware alone to stay private. Bunny's edge rules let us block those routes at the CDN, before requests ever reach origin. Defense in depth costs one API call per rule.
4. Decide what never goes on the CDN — and write it down. Not everything belongs behind cdn.ciphera.net. Favicons, robots.txt, sitemap.xml, and PWA manifests stay at the origin, because browsers and crawlers expect them there and some flatly require it. Everything else follows a written rule: images are never committed to a frontend repo's public/ directory — they're uploaded to the bucket under a per-project prefix (website/, pulse/, id/) and referenced through a small cdnUrl() helper that falls back to local paths in development. The payoff of that discipline shows up in purging: because assets are immutable and path-versioned, cache purges are rare, deliberate, single-URL API calls — not panicked zone-wide flushes. If your team purges the whole zone more than a couple of times a year, the asset pipeline is telling you something.
What We'd Tell Another EU Team
Choose the CDN like you'd choose a payment processor — as a counterparty, not a utility. Concretely:
- Write the jurisdiction requirement down first, before benchmarks make you fall in love with someone's latency map.
- Read the DPA and the logging policy, not the marketing page. "GDPR-compliant" on a landing page is decoration; anonymization commitments in a signed DPA are a contract.
- Keep origin and edge separable. The CDN should be replaceable in an afternoon; if it isn't, you've built a dependency, not a cache. Our asset CDN passes that test. Our websites do not: they run on Bunny's container platform, so moving them is a rebuild.
- Treat the CDN as untrusted. Serve it opaque, immutable, path-keyed objects and let it do the one thing CDNs are great at.
- One honest caveat: Bunny's software is proprietary. We verify behavior through its DPA, its GDPR commitments, and what we can observe operationally — the same "verify, don't trust" standard we apply elsewhere is only partially available here, and pretending otherwise would be exactly the kind of claim we tell you to distrust.
Jurisdiction is also why this choice sits inside a bigger pattern for us — the same reasoning that keeps our data in Switzerland puts our CDN with an EU operator. That argument, with the receipts, is in why Swiss infrastructure matters for data privacy; and the full inventory of what our stack can and can't see about you is on our trust page.