---
title: "Cloudflare CDN Cache Corruption — naturalHeight:0 Diagnostic"
tags: [cloudflare, cdn, cache, debugging, media, payload-cms]
source: daily/2026-05-14.md
created: 2026-05-15
---

# Cloudflare CDN Cache Corruption — `naturalHeight: 0` Diagnostic

## Symptom

- File replaced on disk (or re-uploaded via CMS)
- HTTP response: `200 OK`, `content-type: image/jpeg` (or video/mp4)
- Browser still renders broken image / blank video
- Response headers include `cache-control: max-age=14400` and `cf-cache-status: HIT`
- File is 896 bytes (or similarly tiny stub) despite correct size on origin

## Root Cause

Cloudflare cached a corrupt or stub response (e.g., a partial upload, a 0-byte placeholder) and continues serving it with HTTP 200. Replacing the file on the origin disk has **zero effect** until the cached response is purged.

## Diagnostic — JavaScript

```javascript
// Run in browser console against any image URL suspected of CDN corruption
const img = new Image()
img.onload = () => console.log('naturalHeight:', img.naturalHeight)
img.onerror = () => console.log('load error')
img.src = 'https://example.com/media/video-thumbnail.jpg'

// naturalHeight === 0 with HTTP 200  →  CDN serving corrupt cached stub
// naturalHeight > 0                  →  image is valid
```

## Fix Options

| Option | Command / Action | Notes |
|--------|-----------------|-------|
| **Purge via Cloudflare dashboard** | Zone → Caching → Purge Cache → Custom URLs | Requires Cloudflare account access |
| **Purge via API** | `curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache"` | Fastest; scriptable |
| **Delete CMS record** | Delete the media entry in Payload CMS (or equivalent) | Frontend falls back to static imports that bypass the poisoned URL |
| **Rename the file** | Upload with a new filename — new URL, no cached entry | Last resort; breaks existing links |

## Payload CMS Workaround

When CDN cache purge is not immediately possible:

1. Delete the media document in Payload CMS admin
2. Commit static asset to repo → frontend `import` uses bundled file (bypasses CDN URL entirely)
3. Re-upload media later once CDN entry expires or is purged

## Key Signal

> **HTTP 200 + `img.naturalHeight === 0`** = CDN serving corrupt data.
> HTTP status is NOT a reliable signal for media validity when CDN is in the path.

## See Also

- [[wiki/concepts/cloudflare-proxied-domain-npm-subpath]] — Cloudflare proxied domains + NPM routing
