◉ANTIGRAVITY LABJP
Articles/App Development
▣ App Development/2026-11-12Advanced

Check Web Font Delivery with an Origin Header, Too: How a Fallback Font Went Unnoticed on My Site for 13 Days

For 13 days, the Latin web font on my own site kept returning 404 and every page was drawn in a fallback face. A plain curl returned 200; only the browser's request, which carries an Origin header, received the stale 404. Here is a script that compares three kinds of request, a setting that keeps long cache lifetimes off error responses, and how to pick a purge that reaches every variant.

web fonts3Cloudflare7CORScaching4curlindie development30Antigravity414

✦ Premium Article

On the evening of September 25, I was going over the pages of my own portfolio site and opened document.fonts in the DevTools console. Of the faces listed, exactly one, the Latin font I serve myself, showed status: "error".

Yet when I fetched the same font URL with curl from my machine, it came back 200. The file was on the server, and the byte count matched. The server was right and only the browser was failing. It took me 13 days to notice that gap at all.

As an indie developer who runs his own sites, I tend to stop checking at "curl says 200, so we're fine." I certainly did. What follows is what was happening, how I changed the way I check, and the settings I added so I don't step into the same hole twice.

The short version

A 200 from a plain curl does not prove what the browser receives when it loads a font.

Browsers fetch web fonts with CORS, so the request carries an Origin header. In my setup, the CDN (Cloudflare) kept the response to Origin requests separately from the response to plain requests, and the old 404 survived only on the Origin side. A plain curl sends no Origin, so it only ever saw the healthy copy.

Check with three requests side by side: plain, with Origin, and cache-busting. If you take one thing away, take that.

What happened over those 13 days

Here is the timeline, taken from my own work notes.

DateWhat happenedWhat I could see
Sep 12Switched the Latin face from Google Fonts to a self-hosted subsetNothing broke; I took the switch as done
Sep 12 onwardEvery page rendered the OS fallback (Helvetica) instead of that faceThe difference was small enough that I missed it
Sep 25Found status: "error" in document.fontsPlain curl: 200 with the right byte count
Sep 25Saw 404 from curl with Origin; cleared it with a prefix purgeAll three requests returned 200

I had a metric-matched fallback face in place, so the layout never shifted and the text looked almost right. I think that is exactly why the problem lived so long. A bug that does not break the page outlives one that does.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Right after publishing a new font or image, you can line up a plain request, an Origin request, and a cache-busting request in under a minute and see what the browser actually receives
✦You can prevent the accident where a 404 for a not-yet-uploaded file is kept for a year, with a single setting that stops error responses from being stored
✦When a purge does not fix things, you can decide whether a URL purge, a URL-with-headers purge, or a prefix purge will reach every cached variant
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

▣ App Dev2026-10-20
My PHP API Returned no-store and Cloudflare Passed Everything Through: Overriding Only the Edge TTL
The list API behind my wallpaper apps returned no-store, so putting Cloudflare in front of it changed nothing: every request still reached the origin. This note covers a Cache Rule that overrides only the edge TTL, a small script that proves it works, and a deploy step that pairs upload and purge so stale responses don't linger.
▣ App Dev2026-10-20
I Edited a Sentence and Only the New Characters Turned into Tofu: Adding a Missing-Glyph Check to a Subsetted Japanese Web Font
Change one line of Japanese copy and the newly added characters can render as tofu boxes. Here is a small script that lists every character missing from a subsetted font, with file and line number, and fails the build if even one is absent, with real output from running it.
▣ App Dev2026-10-15
I Swapped an Immutable Asset at the Same URL, and Only the Browser Kept the Old Copy
Overwrite a long-cached JS file at the same URL and neither a CDN purge nor a refresh will make the browser fetch it again. Four delivery styles — same URL, no-cache, query string, content-hash filename — compared in Chromium, plus a complete build-step script that adds the hash.