Fastly Instant Purge

When you ship a corrected asset — a patched JavaScript bundle, an updated stylesheet — stale bytes sitting in edge caches can reach users for minutes or hours if your CDN does not propagate invalidation rapidly. Fastly’s Instant Purge resolves this in under 150 milliseconds across its entire global network, turning a multi-minute wait into a near-real-time operation.

When to Use Fastly Instant Purge

Instant Purge is the right tool in the following situations:

  • Emergency rollback of a bad deploy. A broken asset is live and needs to stop reaching users within seconds, not the 5–10 minutes that AWS CloudFront invalidation typically requires.
  • Purging HTML entry points after a new fingerprinted release. Fingerprinted JS and CSS assets are immutable by design — their content hash makes them self-versioning — but the HTML that references them is not, and it must be purged on every deploy.
  • Batch invalidation of a release group. You tagged every asset in a release with a shared Surrogate-Key and need to atomically remove the entire group in a single API call rather than listing hundreds of URLs.
  • Soft purge for graceful degradation. You want stale-while-revalidate semantics: mark objects stale so the next request triggers background revalidation and users never receive an error response during the fetch.

Prefer Instant Purge over alternatives when:

Scenario Use Instant Purge Use alternative
Sub-second invalidation required Yes
Purging fingerprinted assets that will never change No — serve them indefinitely Build a new URL instead
Purging HTML at CDN with no Fastly subscription Cloudflare cache purge or Nginx
Self-hosted Nginx with no external CDN Nginx proxy cache purge
Budget-constrained and invalidation latency of minutes is acceptable CloudFront invalidation

Prerequisites

Requirement Version / Detail
Fastly service Any paid plan; Instant Purge is available on all tiers
FASTLY_TOKEN API token Created in the Fastly web UI under Account → API Tokens; requires purge_all scope for purge-all, purge_select for URL and surrogate-key purges
SERVICE_ID Visible on the Fastly dashboard service overview page
curl 7.68+ or equivalent HTTP client Needed to send PURGE method requests
Origin emitting Surrogate-Key header Required for tag-based batch purge; VCL override or origin code change
VCL access (optional) Required to set resp.http.Surrogate-Key in vcl_deliver if your origin cannot emit it

Surrogate-key purge requires either your origin to emit Surrogate-Key response headers or a VCL snippet in vcl_deliver to inject them. If you have no VCL access, you must emit the header from application code.

Purge Configuration Reference

Key / Header Type Default Effect
Fastly-Soft-Purge Request header (integer) 0 (hard purge) Set to 1 to mark the object stale rather than deleting it; triggers stale-while-revalidate behaviour
Surrogate-Key Response header (space-separated strings) Not set Associates the cached object with one or more tags; enables tag-based batch purge
Surrogate-Control Response header Not set Overrides Cache-Control TTL at the edge only; Fastly strips this header before forwarding the response to the browser
Cache-Control Response header Origin-provided Sent to the browser verbatim after Fastly strips Surrogate-Control; controls browser TTL and immutable directive
stale-while-revalidate Cache-Control directive Not set After a soft purge marks an object stale, this directive tells Fastly how long to serve stale while revalidating in the background
stale-if-error Cache-Control directive Not set Instructs Fastly to serve a stale object if the origin returns a 5xx error during revalidation
Fastly-Key Request header Required Authenticates purge API requests; value is your API token
purge_all API endpoint REST endpoint POST /service/{id}/purge_all Wipes all objects in the service; use with caution in production

Step-by-Step Implementation

1. Emit Surrogate-Key and Surrogate-Control Headers from the Origin

Your origin must attach a Surrogate-Key header to every cacheable response. Group related assets under a shared tag so you can purge the group atomically.

# Example: Python/Flask origin server
from flask import Flask, send_from_directory, make_response

app = Flask(__name__)

@app.route('/assets/<path:filename>')
def serve_asset(filename):
    response = make_response(send_from_directory('dist/assets', filename))
    # Tag with release version; Fastly strips before browser sees it
    response.headers['Surrogate-Key'] = 'release-v2-5-0 static-assets'
    # Fastly reads this and does NOT forward it to the browser
    response.headers['Surrogate-Control'] = 'max-age=31536000'
    # Browser receives this — fingerprinted assets are immutable
    response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
    return response
# Nginx origin (upstream of Fastly): add headers for all fingerprinted assets
location ~* ^/assets/[a-z0-9._-]+\.[a-f0-9]{8,}\.(js|css|png|jpg|svg|woff2)$ {
    root /srv/app/dist;
    add_header Surrogate-Key "release-v2-5-0 static-assets";
    add_header Surrogate-Control "max-age=31536000";
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Vary "Accept-Encoding";
}

Hash length defaults to 8 hex characters in these examples. Use 12–16 characters in monorepos with thousands of chunks to reduce collision probability.

2. Set Surrogate-Key in VCL (if origin cannot emit the header)

If your origin code cannot be changed, inject the tag in a VCL vcl_deliver subroutine. This requires VCL access in your Fastly service configuration.

sub vcl_deliver {
  # Tag fingerprinted assets with the release version
  if (req.url.path ~ "^/assets/[a-z0-9._-]+\.[a-f0-9]{8,}\.(js|css|png|jpg|svg|woff2)$") {
    set resp.http.Surrogate-Key = "release-v2-5-0 static-assets";
  }

  # Tag HTML entry points separately so they can be purged independently
  if (req.url ~ "\.(html)$" || req.url == "/") {
    set resp.http.Surrogate-Key = "release-v2-5-0 html-entry-points";
  }
}

The vcl_deliver subroutine runs just before Fastly sends the response to the client. Because Fastly automatically strips Surrogate-Key (and Surrogate-Control) headers from the response it forwards to the browser, end users never see these internal tags.

3. Purge by URL (Single Object)

A URL purge removes or marks stale a single cached object. This is the most targeted operation — use it when you know exactly which file changed and want zero blast radius.

# Hard purge: object is immediately deleted from all POPs
curl -X PURGE \
  -H "Fastly-Key: $FASTLY_TOKEN" \
  "https://your-service.global.ssl.fastly.net/assets/app-a1b2c3d4.js"

# Soft purge: object is marked stale; next request triggers background revalidation
curl -X PURGE \
  -H "Fastly-Soft-Purge: 1" \
  -H "Fastly-Key: $FASTLY_TOKEN" \
  "https://your-service.global.ssl.fastly.net/assets/app-a1b2c3d4.js"

Fastly returns HTTP 200 with a JSON body on success:

{ "status": "ok", "id": "1-1719000000-abc123" }

4. Purge by Surrogate-Key (Batch Purge)

A surrogate-key purge removes all objects tagged with a given key across every Fastly POP in a single API call. This is the recommended approach for purging an entire release at deploy time, as detailed in the Fastly surrogate keys guide.

# Purge all objects tagged with release-v2-5-0
curl -X POST \
  "https://api.fastly.com/service/$SERVICE_ID/purge/release-v2-5-0" \
  -H "Fastly-Key: $FASTLY_TOKEN"

# Soft purge by surrogate-key (mark stale, do not delete)
curl -X POST \
  "https://api.fastly.com/service/$SERVICE_ID/purge/release-v2-5-0" \
  -H "Fastly-Key: $FASTLY_TOKEN" \
  -H "Fastly-Soft-Purge: 1"

# Purge a different tag — e.g. HTML entry points only
curl -X POST \
  "https://api.fastly.com/service/$SERVICE_ID/purge/html-entry-points" \
  -H "Fastly-Key: $FASTLY_TOKEN"

To purge multiple tags in one script without multiple round-trips, chain the calls sequentially in your deploy pipeline:

#!/usr/bin/env bash
set -euo pipefail

RELEASE_TAG="release-v2-5-0"
HTML_TAG="html-entry-points"
PURGE_URL="https://api.fastly.com/service/$SERVICE_ID/purge"

purge_tag() {
  local tag="$1"
  local soft="${2:-0}"
  curl -s -X POST \
    "$PURGE_URL/$tag" \
    -H "Fastly-Key: $FASTLY_TOKEN" \
    -H "Fastly-Soft-Purge: $soft" \
    | jq -r '.status'
}

echo "Purging release assets…"
purge_tag "$RELEASE_TAG" 1   # soft purge assets — allow stale-while-revalidate

echo "Purging HTML entry points…"
purge_tag "$HTML_TAG" 0      # hard purge HTML — users must get fresh markup immediately

echo "Purge complete."

5. Purge All (Full Cache Wipe)

A purge-all removes every object in the service from every POP. Use it sparingly: it causes a cache cold-start, driving 100% of traffic to origin until the cache rewarms.

# Wipe the entire service cache — use only in emergencies
curl -X POST \
  "https://api.fastly.com/service/$SERVICE_ID/purge_all" \
  -H "Fastly-Key: $FASTLY_TOKEN"

When purge-all is safe: staging environments with low traffic, immediately after a major configuration change that renders all previously cached objects incorrect (for example, changing the cache key structure itself), or after a data breach where you must guarantee no stale sensitive content remains at the edge.

When purge-all is dangerous: production services with high traffic where a cold-start would overwhelm the origin; services without request coalescing (Fastly’s shield feature) that would send thousands of simultaneous origin requests per popular object.

6. Soft Purge: Stale-While-Revalidate and Stale-If-Error

A soft purge marks an object stale rather than deleting it. Fastly will continue serving the stale object immediately while revalidating in the background — this is the same semantics as the stale-while-revalidate directive in Cache-Control.

For soft purge to work correctly, the origin must emit either stale-while-revalidate in Cache-Control, or Fastly’s own stale behaviour must be configured in the service settings. Without one of these, a soft-purged object is treated as expired immediately and no grace period is provided.

# Origin response headers for soft-purge-friendly HTML entry points
Cache-Control: public, max-age=60, stale-while-revalidate=86400, stale-if-error=604800
Surrogate-Control: max-age=3600
Surrogate-Key: html-entry-points release-v2-5-0

With stale-if-error=604800, if your origin returns a 5xx error during background revalidation, Fastly will continue serving the last good cached version for up to 7 days. This is a critical reliability backstop for HTML entry points during a bad deploy — users see old content rather than an error page. The rolling back a bad deploy on Fastly guide covers the full recovery procedure.

7. Surrogate-Control vs Cache-Control: The Header Split

Fastly reads Surrogate-Control in preference to Cache-Control when making edge TTL decisions, then strips it before forwarding the response to the browser. The browser only ever sees Cache-Control.

This gives you independent control over two TTLs:

# What the origin emits:
Surrogate-Control: max-age=86400       ← Fastly edge TTL: 24 hours
Cache-Control: public, max-age=300     ← Browser TTL: 5 minutes

# What Fastly forwards to the browser:
Cache-Control: public, max-age=300     ← Surrogate-Control is stripped and gone

For fingerprinted assets you typically want both TTLs to be one year, because the content hash guarantees freshness. For HTML entry points, a short browser TTL combined with a longer edge TTL lets you purge at the CDN without relying on users clearing their browser cache.

# HTML entry point header split
Surrogate-Control: max-age=3600, stale-while-revalidate=86400
Cache-Control: public, max-age=60, stale-while-revalidate=86400, stale-if-error=604800
Surrogate-Key: html-entry-points release-v2-5-0

Fastly Instant Purge Flow

Fastly Instant Purge propagation sequence A sequence diagram showing the purge lifecycle: a deploy pipeline sends a purge API call to Fastly, which propagates the invalidation to all global POPs within 150 milliseconds, marks old objects stale or deleted, and serves fresh content from origin for the next request. Fastly Instant Purge — Global Propagation (<150 ms) Deploy Pipeline new assets uploaded to origin t = 0 ms POST /purge/tag Fastly API validates token queues propagation t ≈ 20 ms fan-out signal All POPs objects deleted or marked stale t < 150 ms cache miss Origin serves new asset cached at edge t = next req soft purge → stale, not deleted (serves stale while revalidating) hard purge → object evicted from all POPs immediately first request after purge → cache MISS → origin fetch fresh response stored; subsequent requests → HIT Soft purge (stale) Hard purge (evict) Cache miss → fresh fill
Fastly Instant Purge propagates from API call to all POPs in under 150 ms. Soft purge marks objects stale for graceful revalidation; hard purge evicts immediately.

Verification Commands

After triggering a purge, verify propagation with targeted header inspection.

# 1. Confirm an object was purged (should return MISS or a fresh Age: 0 response)
curl -sI \
  -H "Fastly-Debug: 1" \
  "https://your-service.global.ssl.fastly.net/assets/app-a1b2c3d4.js" \
  | grep -Ei "^(x-cache|fastly-debug|surrogate-key|cache-control|age|surrogate-control):"

# Expected after a hard purge:
# X-Cache: MISS                   ← object was evicted
# Cache-Control: public, max-age=31536000, immutable
# Age: 0                          ← freshly fetched from origin

# Expected after a soft purge (before first revalidation request):
# X-Cache: HIT                    ← stale object still served
# Age: 86421                      ← original age, now stale
# X-Served-By: ...

# 2. Confirm Surrogate-Key header is being emitted by origin
curl -sI "https://your-service.global.ssl.fastly.net/assets/app-a1b2c3d4.js" \
  -H "Fastly-Debug: 1" \
  | grep -i surrogate

# You should see Surrogate-Key in the debug headers (Fastly strips it from normal responses)

# 3. Verify Surrogate-Control is stripped (browsers should never see it)
curl -sI "https://your-service.global.ssl.fastly.net/assets/app-a1b2c3d4.js" \
  | grep -i surrogate-control
# No output expected — Fastly strips this before forwarding

# 4. Check Age header to confirm cache warming after purge
sleep 5
curl -sI "https://your-service.global.ssl.fastly.net/assets/app-a1b2c3d4.js" \
  | grep -i "^age:"
# Should show a small value (e.g. Age: 5) confirming the object was re-cached

# 5. Confirm purge API authentication works
curl -s -o /dev/null -w "%{http_code}" \
  -X POST \
  "https://api.fastly.com/service/$SERVICE_ID/purge/test-tag" \
  -H "Fastly-Key: $FASTLY_TOKEN"
# Expect: 200

Edge Cases and Known Issues

Surrogate-Key Header Not Present After Purge

If a subsequent request does not show Surrogate-Key in Fastly debug headers, the origin is not emitting the header and no VCL override is in place. Objects without surrogate keys can only be purged by URL or via purge-all. Diagnose by fetching the origin directly (bypassing Fastly) and checking whether Surrogate-Key appears in the response:

curl -sI https://your-origin.internal/assets/app-a1b2c3d4.js | grep -i surrogate

If no header appears, add it in origin server configuration or via a VCL snippet as shown in Step 2 above.

Purge Scope Mismatch: Shielding and POP Hierarchy

Fastly’s shielding feature routes all origin fetches through a designated shield POP before distributing content to edge POPs. A purge call hits all POPs including the shield. However, if an edge POP has a locally cached copy that was populated from the shield before the purge arrived, there is a narrow race window (typically under 50 ms) where a stale response could be served. This is eliminated by using soft purge with stale-while-revalidate, since the background revalidation will always win before the grace period expires.

Surrogate-Key Size Limit: 16 KB Per Response

Fastly limits the Surrogate-Key header to 16 KB per response. A large monorepo releasing thousands of chunks each tagged individually will hit this limit. The correct pattern is to use coarse-grained tags (release-v2-5-0, static-assets) rather than per-file tags. Per-file granularity should be achieved through URL-based purge, not surrogate keys. The cache key architecture guide covers strategies for keeping the key space manageable.

Purge-All Rate Limits

The purge-all endpoint is rate-limited to one call per service per second on standard plans. In CI/CD pipelines that run multiple deploy jobs in parallel, a race condition can cause a 429 Too Many Requests response. Serialize purge-all calls and add exponential backoff:

#!/usr/bin/env bash
set -euo pipefail

purge_all_with_retry() {
  local attempt=0
  local max_attempts=5
  while [ $attempt -lt $max_attempts ]; do
    local status
    status=$(curl -s -o /dev/null -w "%{http_code}" \
      -X POST \
      "https://api.fastly.com/service/$SERVICE_ID/purge_all" \
      -H "Fastly-Key: $FASTLY_TOKEN")
    if [ "$status" = "200" ]; then
      echo "Purge-all succeeded on attempt $((attempt + 1))"
      return 0
    fi
    attempt=$((attempt + 1))
    sleep $((2 ** attempt))
  done
  echo "Purge-all failed after $max_attempts attempts" >&2
  return 1
}

purge_all_with_retry

Fingerprinted Assets Do Not Require Purging

A common misunderstanding: assets whose filenames embed a content hashapp-a1b2c3d4.js — never need to be purged. Their cache key is unique to their content. Deploying a new version creates a new URL; the old URL continues to serve correctly from cache for its TTL. The ETag vs immutable cache-control reference explains why immutable is safe for these assets. Only the HTML entry points and any non-fingerprinted resources (robots.txt, sitemap.xml, OG images) need active purging on deploy.

Token Permission Errors (403 Forbidden)

A 403 from the purge API means either the token is invalid or the token’s scope does not include the required permission. URL purge and surrogate-key purge require the purge_select scope; purge-all requires purge_all. Create separate tokens per scope and store them in your CI/CD secrets manager, not in source code.

Soft Purge Requires Grace-Period Configuration

Soft purge only has the intended graceful effect if Fastly knows how long to serve stale. Configure this in one of two ways: emit stale-while-revalidate in Cache-Control from the origin, or set a stale TTL in your Fastly service settings under Health Checks → Stale Content. Without one of these, a soft-purged object is treated as expired immediately, making soft and hard purge functionally identical.

Performance Impact

Purge Type Propagation Latency Origin Load After Purge Recommended Use
Hard purge by URL <150 ms One origin request per POP Single known-bad asset
Soft purge by URL <150 ms Background revalidation only HTML entry points with stale-while-revalidate
Hard purge by Surrogate-Key <150 ms One request per tagged object per POP requesting it Full release invalidation
Soft purge by Surrogate-Key <150 ms Background revalidation per object Preferred for HTML at scale
Purge-all <150 ms (signal), then cache cold 100% origin miss until rewarm Emergency only
CloudFront invalidation (comparison) 5–10 min Queued; eventual Acceptable when latency budget permits

Fastly’s sub-150 ms propagation is achieved through its distributed invalidation bus, which fan-outs the purge signal to all POPs simultaneously rather than waiting for each to poll a central invalidation queue. This architecture means purge latency does not scale with the number of POPs — adding a new region does not increase purge propagation time.

Soft purge has essentially zero additional origin load compared to no purge at all: the background revalidation request for each object happens lazily on the next cache hit, and Fastly coalesces simultaneous revalidation requests for the same object into a single origin fetch (request coalescing). This makes soft-by-surrogate-key the safest option for purging large asset groups in production.

Frequently Asked Questions

Should I purge fingerprinted JS and CSS assets after every deploy?

No. Fingerprinted assets whose filenames contain a content hash are immutable by definition — their URL uniquely identifies their content. Deploying new versions creates new URLs; the old URLs remain valid and correctly cached. Purge only the HTML entry points (and any non-fingerprinted resources such as robots.txt or favicon.ico) that reference the new hashed URLs. This is the most important operational distinction when working with a content-hashing build pipeline.

What is the difference between hard purge and soft purge, and when should I choose soft?

A hard purge deletes the cached object immediately. The next request is a cache miss and goes to origin, potentially causing a thundering herd if thousands of users request the same object simultaneously. A soft purge marks the object stale; Fastly serves the stale version to the next visitor while simultaneously sending a background revalidation request to origin. Choose soft purge for HTML entry points and any resource where a brief period of stale content is acceptable but a cache miss storm is not. Choose hard purge when serving any stale version — even for 50 ms — is unacceptable (for example, after invalidating security-sensitive content).

Does Fastly’s Surrogate-Control header reach the browser?

No. Fastly strips Surrogate-Control from all responses before forwarding them to the client. The browser only ever sees Cache-Control. This means you can set a very long edge TTL in Surrogate-Control (enabling aggressive caching at the CDN) while independently setting a shorter browser TTL in Cache-Control — and you retain the ability to purge the edge copy at any time without touching the browser cache.

How do I integrate Fastly purge into a CI/CD pipeline?

Add a purge step at the end of your deploy job, after new assets are uploaded to origin. Purge HTML entry points by surrogate key (html-entry-points or release-v{version}) using a hard purge, and optionally issue a soft purge for the previous release’s assets to accelerate revalidation. Store SERVICE_ID and FASTLY_TOKEN as encrypted CI/CD secrets. The CI/CD asset pipeline integration guide shows how to wire this into GitHub Actions alongside hash manifest generation and atomic CDN deploy steps.