What's Actually Happening
You updated your website content - new images, updated CSS, fresh JavaScript, or changed HTML - and immediately requested a CDN cache purge to invalidate the old cached versions. But users are still seeing the old content hours later. The purge request shows as pending, stuck in queue, or completed but without effect. Your CDN isn't clearing cached objects as expected.
This happens across all major CDN providers: Cloudflare, Akamai, AWS CloudFront, Fastly, and others. The purge/invalidation mechanisms vary but the symptoms are similar - stale content persists despite your efforts to clear it.
The Error You'll See
CDN cache purge issues manifest differently depending on your provider:
```bash # Cloudflare purge pending/stuck $ curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"files":["https://example.com/css/style.css"]}'
{ "result": { "id": "abc123-def456" }, "success": true, "errors": [], "messages": [] }
# Checking purge status: $ curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache/abc123-def456"
{ "result": { "id": "abc123-def456", "status": "pending" # Stuck in pending for hours! } }
# Cloudflare API errors: { "success": false, "errors": [ { "code": 1004, "message": "Purge rate limited. Maximum 1000 purge requests per minute." } ] }
# CloudFront invalidation stuck $ aws cloudfront create-invalidation \ --distribution-id $DIST_ID \ --paths "/css/*" "/js/*"
{ "Invalidation": { "Id": "I1234567890ABC", "Status": "InProgress", "CreateTime": "2026-04-08T10:15:23.456Z" } }
# Checking hours later: $ aws cloudfront get-invalidation \ --distribution-id $DIST_ID \ --id I1234567890ABC
{ "Invalidation": { "Status": "InProgress" # Still in progress! } }
# CloudFront error - too many invalidations: An error occurred (TooManyInvalidationsInProgress) when calling the CreateInvalidation operation: Your request cannot be processed because you have exceeded the maximum number of concurrent invalidation requests. The limit is 15 concurrent invalidations per distribution.
# Akamai purge API error $ curl -X POST "https://api.ccu.akamai.com/ccu/v3/invalidate/url" \ -H "Authorization: EGWS-HMAC-SHA256 ..." \ -d '{"objects":["https://example.com/css/style.css"]}'
{ "httpStatus": 429, "detail": "Rate limit exceeded. Please retry after 60 seconds.", "estimatedTime": 3600 # 1 hour estimated completion }
# Akamai Fast Purge stuck: { "purgeId": "abc123", "progress": 45, # Stuck at 45% for hours "purgeStatus": "In-Progress" }
# Fastly purge failure $ curl -X POST "https://api.fastly.com/purge/example.com/css/style.css" \ -H "Fastly-Key: $FASTLY_TOKEN"
{ "status": "ok" }
# But content still cached: $ curl -I https://example.com/css/style.css HTTP/2 200 Cache-Control: max-age=31536000 Age: 3600 # Content is still 1 hour old! X-Cache: HIT
# Fastly soft purge not working: $ curl -X POST "https://api.fastly.com/purge/example.com/css/style.css" \ -H "Fastly-Key: $FASTLY_TOKEN" \ -H "Fastly-Soft-Purge: 1"
{ "status": "ok" }
# Soft purge sets TTL to 0 but stale content still served: # Surrogate-Key purges not working ```
Additional symptoms: - Purge API returns success but content remains cached - Invalidation stuck in "pending" or "in-progress" status for extended time - Users report seeing old content hours after purge - Different users see different content versions - CDN shows cached content with high Age header - Purge rate limited or quota exceeded errors - Some paths purge successfully while others fail
Why This Happens
- 1.Purge Queue Backlog: The CDN provider's purge processing queue is overloaded. Too many customers requested purges simultaneously, creating a backlog. Your purge request sits in queue waiting to be processed, sometimes for hours during peak times.
- 2.Rate Limiting or Quota Exceeded: You exceeded the CDN's purge rate limits. Cloudflare limits Enterprise to 1000/min, CloudFront limits 15 concurrent invalidations per distribution, Akamai has request rate limits. Once exceeded, your requests are rejected or queued with delays.
- 3.Incorrect Purge Path or URL: The purge request targets paths or URLs that don't match how content is actually cached. Wildcard patterns don't work as expected, URL encoding mismatches, or case sensitivity differences cause purges to miss the cached objects.
- 4.Edge Location Propagation Delay: CDN has hundreds of edge locations globally. Purge must propagate to all edges before content is truly invalidated. Some CDNs process purges asynchronously with propagation taking 5-60 minutes depending on provider.
- 5.Cache-Control Headers Override Purge: Strong Cache-Control headers with long max-age or immutable directive cause some CDN behaviors that resist purging. Objects with very long TTLs may be handled differently by the purge mechanism.
- 6.Object Not Actually Cached: You requested purge for content that wasn't cached by CDN. The object bypassed caching due to cache-control: no-store, authorization headers, dynamic content markers, or Vary header complications.
- 7.Surrogate-Key or Cache-Tag Not Applied: You're trying to purge by cache tag/surrogate-key, but the responses never included the proper headers. Cloudflare cache-tags, Fastly surrogate-keys, Akamai cache tags weren't set on the objects, so purge by tag finds nothing.
- 8.Multiple Cache Layers: Content is cached by multiple layers - CDN, browser cache, origin server cache, application cache. You purged CDN but browser still caches, or Nginx/Varnish at origin still serves stale content, creating the illusion of failed purge.
Step 1: Identify Your CDN Provider and Check Current Cache Status
First, determine which CDN you're using and verify what's currently cached.
```bash # Check which CDN serves your site: curl -I https://example.com/css/style.css
# Look for CDN-specific headers: # Cloudflare: cf-cache-status, server: cloudflare # CloudFront: X-Amz-Cf-Id, X-Amz-Cf-Pop # Akamai: X-Akamai-Staging, Akamai-Origin-Hops # Fastly: X-Served-By, X-Cache, X-Fastly-Request-ID
# Example Cloudflare response: HTTP/2 200 server: cloudflare cf-cache-status: HIT age: 3600 cf-ray: abc123-DEF
# Example CloudFront response: HTTP/2 200 x-amz-cf-id: ABC123== x-amz-cf-pop: IAD50 x-cache: Hit from cloudfront
# Example Fastly response: HTTP/2 200 x-served-by: cache-abc123-DEF x-cache: HIT, HIT x-fastly-request-id: abc123
# Check Age header (how stale the content is): curl -sI https://example.com/css/style.css | grep Age # Age: 3600 = content is 1 hour old
# Check multiple CDN nodes: # Cloudflare - test different edges: curl -I "https://example.com/css/style.css" -H "cf-connecting-to: 1.1.1.1" curl -I "https://example.com/css/style.css" -H "cf-connecting-to: 8.8.8.8"
# CloudFront - test from different regions: curl -I "https://d123.cloudfront.net/css/style.css" -H "X-Forwarded-For: 1.2.3.4"
# Fastly - check specific POP: curl -I https://example.com/css/style.css -H "Fastly-Debug-Header: true"
# Check what Cache-Control headers are on the response: curl -sI https://example.com/css/style.css | grep -i cache-control
# Check surrogate keys/cache tags if present: curl -sI https://example.com/css/style.css | grep -i "surrogate-key|cache-tag" ```
Document your CDN provider, current Age of cached content, and any special cache headers.
Step 2: Check Pending Purge/Invalidation Status
Check the status of your previous purge requests.
```bash # For Cloudflare: # Get zone ID: curl -s "https://api.cloudflare.com/client/v4/zones?name=example.com" \ -H "Authorization: Bearer $CF_TOKEN" | jq '.result[].id'
ZONE_ID="your-zone-id"
# List recent purge requests: curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN"
# For enterprise, check purge status: curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache/$PURGE_ID" \ -H "Authorization: Bearer $CF_TOKEN" | jq '.result.status'
# For CloudFront: # List invalidations: aws cloudfront list-invalidations --distribution-id $DIST_ID
# Check specific invalidation: aws cloudfront get-invalidation \ --distribution-id $DIST_ID \ --id I1234567890ABC \ --output json | jq '.Invalidation.Status'
# Count invalidations in progress:
aws cloudfront list-invalidations \
--distribution-id $DIST_ID \
--query 'InvalidationList.Items[?Status==InProgress]' \
--output json | jq 'length'
# For Akamai: # Check purge status: curl -s "https://api.ccu.akamai.com/ccu/v3/purge-status/$PURGE_ID" \ -H "Authorization: EGWS-HMAC-SHA256 ..." | jq '.progress, .purgeStatus'
# For Fastly: # Check purge history: curl -s "https://api.fastly.com/service/$SERVICE_ID/purge" \ -H "Fastly-Key: $FASTLY_TOKEN" \ -H "Accept: application/json"
# Check specific service purges: curl -s "https://api.fastly.com/service/$SERVICE_ID/version/$VERSION/purge" \ -H "Fastly-Key: $FASTLY_TOKEN"
# Look for stuck/in-progress purges: # Cloudflare: status "pending" # CloudFront: status "InProgress" # Akamai: purgeStatus "In-Progress", progress stuck at percentage # Fastly: no direct status API, check Age header ```
Step 3: Check Purge Rate Limits and Quotas
Verify you haven't exceeded provider limits.
```bash # Cloudflare rate limits: # Enterprise: 1000 purge requests per minute, 30,000 files per purge # Business: 5,000 total purge requests per hour # Pro: 1,000 total purge requests per hour # Free: Limited to 1,000 total purge requests per month
# Check your zone type: curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID" \ -H "Authorization: Bearer $CF_TOKEN" | jq '.result.plan.name'
# CloudFront limits: # 15 concurrent invalidations per distribution # 3,000 invalidation paths per invalidation request
# Count current invalidations: aws cloudfront list-invalidations \ --distribution-id $DIST_ID \ --max-items 20 | jq '.InvalidationList.Items | length'
# If 15 concurrent invalidations, can't create new ones until some complete
# Akamai CCU limits: # Depends on contract, typically ~100 requests per 5 minutes # Check rate limit headers in API response: curl -I "https://api.ccu.akamai.com/ccu/v3/invalidate/url" \ -H "Authorization: ..." \ -d '{"objects":["url"]}'
# Look for: X-RateLimit-Limit, X-RateLimit-Remaining
# Fastly limits: # Soft purge: 5,000 per second per service # Instant purge: 5 per second per service # All keys purge: 1 per hour per service
# Check Fastly service settings: curl -s "https://api.fastly.com/service/$SERVICE_ID" \ -H "Fastly-Key: $FASTLY_TOKEN" | jq '.' ```
Step 4: Purge with Correct URL/Path Format
Ensure your purge request matches how content is cached.
```bash # For Cloudflare - purge by URL: curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"files":["https://example.com/css/style.css"]}'
# Purge multiple files: curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"files":["https://example.com/css/style.css","https://example.com/js/app.js"]}'
# Purge by cache-tag (Enterprise only): curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"tags":["css","homepage"]}'
# Purge everything: curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"purge_everything":true}'
# For CloudFront - create invalidation: aws cloudfront create-invalidation \ --distribution-id $DIST_ID \ --paths "/css/style.css" "/js/app.js"
# Invalidating all objects under path: aws cloudfront create-invalidation \ --distribution-id $DIST_ID \ --paths "/css/*" "/images/*"
# Wildcard must be at end of path only!
# Invalidating everything: aws cloudfront create-invalidation \ --distribution-id $DIST_ID \ --paths "/*"
# For Akamai - Fast Purge: curl -X POST "https://api.ccu.akamai.com/ccu/v3/invalidate/url" \ -H "Authorization: EGWS-HMAC-SHA256 ..." \ -H "Content-Type: application/json" \ -d '{"objects":["https://example.com/css/style.css"]}'
# Purge by cache tag: curl -X POST "https://api.ccu.akamai.com/ccu/v3/invalidate/tag" \ -H "Authorization: ..." \ -d '{"objects":["css-tag"]}'
# For Fastly - instant purge by URL: curl -X PURGE https://example.com/css/style.css
# Or via API: curl -X POST "https://api.fastly.com/purge/example.com/css/style.css" \ -H "Fastly-Key: $FASTLY_TOKEN"
# Purge by surrogate key: curl -X POST "https://api.fastly.com/service/$SERVICE_ID/purge" \ -H "Fastly-Key: $FASTLY_TOKEN" \ -H "Surrogate-Key: css homepage"
# Purge all: curl -X POST "https://api.fastly.com/service/$SERVICE_ID/purge_all" \ -H "Fastly-Key: $FASTLY_TOKEN"
# Important: URL must match exactly how it's cached: # Include or exclude query string? Purge both versions: curl -X POST ... --data '{"files":["https://example.com/page","https://example.com?page=1"]}'
# Case sensitivity: Some CDNs are case-sensitive # Purge both case variants if uncertain ```
Step 5: Check Origin Server Cache-Control Headers
Verify your origin sends appropriate headers for caching.
```bash # Bypass CDN and check origin directly: # Cloudflare - use origin server directly: curl -sI "https://origin-server.example.com/css/style.css" \ -H "Host: example.com"
# Or use the resolved origin IP: curl -sI "https://$ORIGIN_IP/css/style.css" \ -H "Host: example.com"
# CloudFront - check origin directly: curl -sI "https://origin.example.com/css/style.css"
# Check Cache-Control header: curl -sI https://origin-server.example.com/css/style.css | grep -i cache-control
# Common issues: # - no-store: CDN won't cache # - private: CDN won't cache (unless configured to) # - max-age=0: Immediately stale # - must-revalidate: Always checks origin
# Check if origin sends surrogate-key/cache-tag: curl -sI https://origin-server.example.com/css/style.css | grep -i "surrogate-key|cache-tag"
# If missing, tag-based purges won't work!
# For application servers: # Nginx - check config: cat /etc/nginx/nginx.conf | grep -A10 "location.*css"
# Should include: # add_header Cache-Control "public, max-age=31536000"; # add_header Surrogate-Key "css";
# Apache - check config: cat /etc/apache2/sites-available/example.conf | grep -i cache-control
# For WordPress/Drupal - check caching plugins # Varnish - check VCL: cat /etc/varnish/default.vcl | grep -i cache-control ```
Step 6: Clear Multiple Cache Layers
Purge or bypass caches at all levels including browser and origin.
```bash # Browser cache - add query string to force bypass: curl -I "https://example.com/css/style.css?v=2"
# Or use version in filename (best practice): curl -I "https://example.com/css/style.v2.css"
# Origin server cache - clear Nginx proxy cache: rm -rf /var/cache/nginx/*
# Clear Varnish cache: varnishadm ban "obj.http.X-Surrogate-Key ~ css" varnishadm ban.url "/css/style.css"
# Restart application cache: systemctl restart php-fpm systemctl restart node-app
# For WordPress: wp cache flush wp transient delete --all
# For Drupal: drush cache:rebuild
# For Magento: bin/magento cache:flush
# Check all layers are cleared: curl -I https://example.com/css/style.css
# Age header should be 0 or very low # X-Cache should show MISS (not HIT)
# Test from multiple locations/CDN nodes: # Use different tools to verify global purge:
# Web-based testers: # https://tools.keycdn.com/curl # https://tools.ipip.net/curl.php
# Or test from different regions via VPN ```
Step 7: Use Emergency Purge Methods
When normal purges are stuck, use emergency techniques.
```bash # For Cloudflare - Dev Mode (bypasses cache temporarily): curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/development_mode" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"value":"on"}'
# Development mode bypasses cache for 3 hours # All requests go to origin
# Check dev mode status: curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/development_mode" \ -H "Authorization: Bearer $CF_TOKEN" | jq '.result.value'
# Turn off dev mode: curl -X PATCH ... --data '{"value":"off"}'
# For CloudFront - increase TTL temporarily: # Update distribution config to lower cache TTL: aws cloudfront get-distribution-config --id $DIST_ID > /tmp/cf-config.json
# Edit config to change DefaultTTL to 0 temporarily # Then update: aws cloudfront update-distribution --id $DIST_ID \ --distribution-config file:///tmp/cf-config-updated.json
# For Fastly - bypass cache via VCL: curl -X PUT "https://api.fastly.com/service/$SERVICE_ID/version/$ACTIVE_VER/vcl_snippets/bypass" \ -H "Fastly-Key: $FASTLY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"bypass","type":"recv","content":"set req.http.Fastly-Cache-Control = \"no-store\";"}'
# For Akamai - use Enhanced purge (contract dependent): curl -X POST "https://api.ccu.akamai.com/ccu/v3/delete" \ -H "Authorization: ..." \ -d '{"objects":["url"]}'
# Alternative: Change object URL # Rename css/style.css to css/style.v2.css # New URL = new cache object
# Emergency URL change in HTML: sed -i 's/style.css/style.v2.css/g' *.html
# Deploy new HTML with new asset URLs # Old cached content orphaned, never accessed ```
Step 8: Monitor Purge Completion
Verify purge completed and content is fresh.
```bash # Check Age header periodically: for i in {1..10}; do curl -sI https://example.com/css/style.css | grep Age sleep 60 done
# Age should decrease or show 0 after purge
# Check X-Cache status: curl -sI https://example.com/css/style.css | grep -i x-cache
# Cloudflare: cf-cache-status should be DYNAMIC, MISS, or EXPIRED # CloudFront: x-cache should be Miss from cloudfront # Fastly: x-cache should be MISS
# Check from multiple CDN nodes: # Cloudflare - test from different regions: for region in US EU AP; do curl -sI "https://example.com/css/style.css" -H "CF-IPCountry: $region" | grep Age done
# Verify content is actually updated: curl -s https://example.com/css/style.css | grep "version-2" # Should find new content
# Compare old vs new: curl -s https://example.com/css/style.css | md5sum # Should match new file checksum
# Check invalidation status again: # CloudFront: aws cloudfront get-invalidation \ --distribution-id $DIST_ID \ --id $INVALIDATION_ID | jq '.Invalidation.Status'
# Should be "Completed"
# Cloudflare: curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache/$PURGE_ID" \ -H "Authorization: Bearer $CF_TOKEN" | jq '.result.status'
# Should not be "pending" ```
Step 9: Update Deployment Process to Prevent Future Issues
Improve your deployment workflow to avoid purge problems.
```bash # Best practice: Version all assets # Instead of style.css, use style.v2.css or style.css?v=2
# In build process: cat > scripts/build-assets.sh << 'EOF' #!/bin/bash VERSION=$(git rev-parse --short HEAD)
# Copy assets with version cp src/css/style.css dist/css/style.$VERSION.css cp src/js/app.js dist/js/app.$VERSION.js
# Update references in HTML sed -i "s/style.css/style.$VERSION.css/g" dist/*.html sed -i "s/app.js/app.$VERSION.js/g" dist/*.html
echo "Assets versioned: $VERSION" EOF
chmod +x scripts/build-assets.sh
# Use cache tags/surrogate keys in application: # For HTTP responses, add headers:
# Nginx config: ```
location ~* \.css$ {
add_header Cache-Control "public, max-age=31536000";
add_header Surrogate-Key "css assets";
}# Apache config:<FilesMatch "\.css$">
Header set Cache-Control "public, max-age=31536000"
Header set Surrogate-Key "css assets"
</FilesMatch>```bash # In PHP application: header('Cache-Control: public, max-age=31536000'); header('Surrogate-Key: page homepage articles');
# Then purge by tag instead of URL: curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -d '{"tags":["homepage"]}'
# Add purge to deployment script: cat > scripts/deploy.sh << 'EOF' #!/bin/bash # Deploy with CDN purge
# 1. Build new version npm run build
# 2. Deploy to origin rsync -avz dist/ origin-server:/var/www/html/
# 3. Purge CDN (if needed for HTML) # CSS/JS are versioned, so no purge needed curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -d '{"tags":["html","homepage"]}'
# 4. Verify deployment sleep 30 curl -sI https://example.com/ | grep Age
echo "Deploy complete" EOF
chmod +x scripts/deploy.sh ```
Step 10: Set Up CDN Monitoring and Automated Purge
Create monitoring for cache issues and automate purge when needed.
```bash # Create cache monitoring script: cat > scripts/check-cdn-cache.sh << 'EOF' #!/bin/bash # CDN Cache Health Check
URL="https://example.com/css/style.css" MAX_AGE=${MAX_AGE:-300} # 5 minutes acceptable age ALERT_EMAIL="ops@company.com"
# Get current Age AGE=$(curl -sI $URL | grep Age | awk '{print $2}' | tr -d '\r')
if [ -z "$AGE" ]; then AGE=0 fi
echo "Cache Age: $AGE seconds"
# Check if content is too stale if [ "$AGE" -gt "$MAX_AGE" ]; then echo "WARNING: Content is stale ($AGE seconds)"
# Check if new content exists at origin ORIGIN_MD5=$(curl -s https://origin-server.example.com/css/style.css | md5sum | awk '{print $1}') CDN_MD5=$(curl -s $URL | md5sum | awk '{print $1}')
if [ "$ORIGIN_MD5" != "$CDN_MD5" ]; then echo "CRITICAL: CDN content differs from origin!"
# Auto-purge if enabled if [ "$AUTO_PURGE" = "true" ]; then echo "Auto-purging CDN..." curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -d "{\"files\":[\"$URL\"]}" fi
# Alert echo "CDN stale content detected. Age: $AGE" | mail -s "CDN Cache Alert" $ALERT_EMAIL
exit 2 fi fi
echo "OK: Cache healthy" exit 0 EOF
chmod +x scripts/check-cdn-cache.sh
# Run check: scripts/check-cdn-cache.sh
# Add to cron: (crontab -l; echo "*/5 * * * * /path/to/scripts/check-cdn-cache.sh") | crontab -
# Create purge automation: cat > scripts/auto-cdn-purge.sh << 'EOF' #!/bin/bash # Automatic CDN Purge on Deploy
ZONE_ID="${ZONE_ID:-}" CF_TOKEN="${CF_TOKEN:-}"
if [ -z "$ZONE_ID" ] || [ -z "$CF_TOKEN" ]; then echo "Error: ZONE_ID and CF_TOKEN required" exit 1 fi
FILES_TO_PURGE="${FILES:-}" TAGS_TO_PURGE="${TAGS:-}"
if [ -n "$FILES_TO_PURGE" ]; then echo "Purging files: $FILES_TO_PURGE" curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"files\":$(echo $FILES_TO_PURGE | jq -R 'split(" ")')}" fi
if [ -n "$TAGS_TO_PURGE" ]; then echo "Purging tags: $TAGS_TO_PURGE" curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"tags\":$(echo $TAGS_TO_PURGE | jq -R 'split(" ")')}" fi
echo "Purge complete. Verify:" sleep 30 curl -sI https://example.com/css/style.css | grep Age
EOF
chmod +x scripts/auto-cdn-purge.sh
# Use in CI/CD: # GitHub Actions: ```
- name: Purge CDN
env:
ZONE_ID: ${{ secrets.CF_ZONE_ID }}
CF_TOKEN: ${{ secrets.CF_TOKEN }}
TAGS: homepage articles
run: scripts/auto-cdn-purge.shChecklist for Fixing CDN Cache Not Purging
| Step | Action | Command | Status | ||
|---|---|---|---|---|---|
| 1 | Identify CDN provider and check cache | `curl -I URL \ | grep -i x-cache\ | cf-cache-status` | ☐ |
| 2 | Check pending purge status | API call to check invalidation status | ☐ | ||
| 3 | Check rate limits and quotas | Count concurrent invalidations | ☐ | ||
| 4 | Purge with correct URL format | curl -X POST purge_api | ☐ | ||
| 5 | Check origin Cache-Control headers | `curl -sI origin \ | grep cache-control` | ☐ | |
| 6 | Clear multiple cache layers | Browser, origin, Varnish | ☐ | ||
| 7 | Use emergency purge methods | Enable dev mode, version assets | ☐ | ||
| 8 | Monitor purge completion | Check Age header decreases | ☐ | ||
| 9 | Update deployment process | Version assets, use cache tags | ☐ | ||
| 10 | Set up CDN monitoring and automation | Create check script, cron job | ☐ |
Verify the Fix
After fixing CDN cache purge issues, verify content is fresh:
```bash # 1. Age header shows fresh content curl -sI https://example.com/css/style.css | grep Age # Age should be 0 or very low (< 60)
# 2. Cache status shows MISS curl -sI https://example.com/css/style.css # Cloudflare: cf-cache-status: MISS or DYNAMIC # CloudFront: x-cache: Miss from cloudfront # Fastly: x-cache: MISS
# 3. Content checksum matches origin ORIGIN_MD5=$(curl -s https://origin-server.example.com/css/style.css | md5sum) CDN_MD5=$(curl -s https://example.com/css/style.css | md5sum) # Should match
# 4. Invalidation status shows Completed aws cloudfront get-invalidation --id $INV_ID | jq '.Invalidation.Status' # "Completed"
# 5. Test from multiple CDN nodes curl -sI https://example.com/css/style.css -H "CF-IPCountry: EU" | grep Age curl -sI https://example.com/css/style.css -H "CF-IPCountry: AP" | grep Age # All should show low Age
# 6. Users report seeing new content # Test in browser with clean cache
# 7. Rate limits not exceeded curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" | jq '.success' # true
# 8. Cache tags/surrogate keys are set curl -sI https://example.com/css/style.css | grep -i surrogate-key # Should show tags
# 9. Future deployments use versioned assets ls dist/css/ # style.v2.css, style.v3.css, etc.
# 10. Monitoring script runs without errors scripts/check-cdn-cache.sh # Exit code 0 ```
Related Issues
- [Fix Cloudflare Firewall Blocking API](/articles/fix-cloudflare-firewall-blocking-api) - Cloudflare access issues
- [Fix Akamai Cache Key Incorrect](/articles/fix-akamai-cache-key-incorrect) - Akamai caching issues
- [Fix Nginx Rate Limiting Not Working](/articles/fix-nginx-rate-limiting-not-working) - Nginx caching and limits
- [Fix Varnish Cache Not Serving](/articles/fix-varnish-cache-not-serving) - Varnish cache issues
- [Fix Browser Cache Stuck](/articles/fix-browser-cache-stuck) - Client-side caching
- [Fix HTTP 304 Not Modified Issues](/articles/fix-http-304-not-modified) - Cache validation
- [Fix AWS CloudFront Distribution Not Working](/articles/fix-aws-cloudfront-distribution-not-working) - CloudFront setup issues