Surrogate Key / Cache Tag

Caching

A surrogate key, or cache tag, is a label the CDN indexes against a cached response alongside its cache key, normally set by the origin in a response header. Purging the label invalidates every object carrying it in one call. It cannot serve a request, only target content for purging.

Also known as cache tag.

9 min read Updated Aug 30, 2026

Full Explanation

A surrogate key (also called a cache tag) is a label: a CDN indexes it against a cached response, in addition to the key it stores that response under. This lets a later invalidation find every response sharing the label. The origin server normally sets it in a response header. The header is Surrogate-Key on Fastly, Cache-Tag on Cloudflare, and Edge-Cache-Tag on Akamai. One request can then purge every object carrying that label, however many URLs that turns out to be.

It is not a cache key. Fastly states the distinction directly: surrogate keys "cannot be used to find and serve the content in a request, but can be used to target the content for purging". A response is still stored and looked up under its cache key. The tags are only an extra index used at purge time. Nor is it a freshness control. Cache-Control and the similarly named Surrogate-Control decide how long an object stays fresh. Surrogate-Control has nothing to do with Surrogate-Key, despite the shared prefix. A tag changes nothing about how an object is stored, matched or expired. It only changes which objects can be invalidated together.

The name comes from RFC 3040. It defines a surrogate as "a gateway co-located with an origin server, or at a different point in the network, delegated the authority to operate on behalf of, and typically working in close co-operation with, one or more origin servers". A surrogate normally serves responses from an internal cache. The header names and their rules are not standardised. Fastly, Cloudflare and Akamai each define their own, and they differ in ways that matter.

How it works

  1. Tags are attached to the response. Conventionally the application sets the header at the origin. The CDN can also add tags itself. Fastly does this by setting the header in VCL during fetch. Akamai does it with a Property Manager Cache Tag behaviour that can build a tag from a variable, such as part of the filename. Either way, the tag arrives with the response before it is cached.
  2. The CDN builds a tag-to-object index. Fastly describes using the keys "to create a mapping from each key to the cached content". The relationship is many-to-many. One response can carry several tags, and the same tag can appear on any number of responses. Fastly's own example indexes a category page against every product it displays. A change to one product then purges all the pages that product appears on, not just its own page.
  3. Tags do not affect freshness or matching. The object keeps the same TTL and the same cache key it would have had without them. Adding a tag never makes a response more or less cacheable.
  4. The header is normally stripped before the client sees it. Fastly removes Surrogate-Key from responses. Akamai says end users "ordinarily" never receive Edge-Cache-Tag. Both allow the tags to be revealed deliberately for debugging. Stripping is the default, rather than a guarantee.
  5. A purge follows the index. You name the tag, not the URLs. The CDN walks its mapping and invalidates each object indexed against that tag, regardless of URL. Akamai purges everything matching any of the tags you submit. Whether that leaves the object deleted or merely stale depends on the purge mode you choose.

Here is the header on a real response. Each CDN uses its own name for it.

// Response with surrogate keys (Fastly format)
HTTP/1.1 200 OK
Content-Type: text/html
Surrogate-Key: product-123 category-shoes homepage-featured
Cache-Control: max-age=86400

// Same concept, Cloudflare format
HTTP/1.1 200 OK
Cache-Tag: product-123,category-shoes,homepage-featured

Why it matters for a CDN

A CDN stores objects by URL. But the events that make content wrong are logical: a price changes, a match score updates, a campaign ends. One such event usually invalidates many independently cached URLs: the product page, the category listing, the search results, the home page module, and every image variant. Purging by URL means the application must enumerate all of them correctly, every time. Any URL it forgets keeps serving stale content until its TTL expires.

Tags move that problem to where the knowledge already lives. The code that renders a page knows which entities it used, so it can label the response with them. The purge pipeline then needs only the entity name. Akamai's worked example is a live-score site whose every play would otherwise require selecting many objects by hand. The site instead tags objects by team and tournament. Akamai also notes that tags let you use natural language, such as fall-sale, rather than a technical identifier like a URL or CP code. The same trick handles cases URLs cannot express at all. Examples include purging all six rendition variants of one uploaded image, or every object under a directory.

What CDNs do

  • Fastly: Surrogate-Key, space-separated, so a key is a single string containing no spaces. Individual keys are limited to 1,024 bytes and the whole header to 16,384 bytes. Purge one key with POST /service/{service_id}/purge/{surrogate_key}, or up to 256 keys per batch request to POST /service/{service_id}/purge. The control panel offers the same under Purge key. Sending Fastly-Soft-Purge: 1 makes the purge soft, marking objects stale rather than immediately inaccessible.
  • Cloudflare: Cache-Tag, comma-separated. A response may carry more than one such header field. Tags cannot contain spaces. Case is not significant. Only printable ASCII is allowed. The aggregate header may not exceed 16 KB after the field name, roughly 1,000 unique tags. In a purge API call, a tag may be up to 1,024 characters. Tag purging is available on every plan. Purge requests are rate limited per account, from 5 per minute on Free to 50 per second on Enterprise. All plans allow at most 100 operations per request.
  • Akamai: Edge-Cache-Tag, comma-separated, one header per object with up to 128 values. If several such headers are sent, only the first is respected. A single tag may not exceed 128 characters. Tags are case-sensitive. They must also match the HTTP token rule that Akamai's documentation still cites from RFC 7230. Tags are scoped to the account. A purge may be an Invalidate or a Delete, on the production or staging network.

Watch out for

  • Limits fail quietly, and an untagged object escapes every purge. Fastly ignores the key it is parsing and all keys after it in the same header once a length limit is hit. Akamai ignores tags beyond 128 per object. It does not purge content whose tag exceeds 128 characters. It also strips tags that break its syntax rules. In each case, the response still caches successfully, the purge still returns success, and the object simply stays. Nothing in the purge result tells you a tag was missing.
  • Tag names collide across teams. Akamai scopes tags at the account level. Its canonical example: two departments both tag content SALE. Either one can then purge the other's content. Namespace tags by team and by environment.
  • A broad purge is a blast radius. Invalidating a tag that covers a large set sends all of it back to the origin at once. That is how a routine purge becomes a cache stampede. The problem is purging the broad tag, not owning one. Use soft purge or Akamai's Invalidate instead, so the edge can serve stale copies while it revalidates.
  • The result of a tag purge is not always a MISS. Cloudflare documents that purging by tag yields CF-Cache-Status: MISS on later requests. With Tiered Cache, though, the lower tier revalidates against the upper tier, so EXPIRED may be returned instead. A test that asserts MISS will fail on a correctly working tiered setup.
  • Retagging existing content does not take effect on its own. Akamai treats tags as part of the tagged content. So new or changed tags need a 200 from the origin, and a 304 does not update them. You must wait for the TTL to lapse, or purge the objects explicitly. Akamai also cannot tag content that is already cached, or when NetStorage is the origin.
  • Vendor rules are not portable. Separator, case sensitivity, permitted characters and length limits all differ. A tag scheme that works on one CDN can be silently truncated or rejected on another.
  • Purging does not stop re-caching. The next request repopulates the object. It is tagged only if that response carries the header again. A code path that omits the header reintroduces an untaggable object.
  • Tag names are not reliably private. Both Fastly and Akamai can expose the header on purpose. So treat tag values as data that may become visible. Keep secrets and internal identifiers out of them.

Best practice

  • Derive tag names deterministically from content identity, such as product-123 or sale-shoes. This lets the renderer and the purge pipeline compute the same string without coordinating.
  • Prefix tags with a team and environment namespace to prevent one group's purge from clearing another's content.
  • Tag each response with both its own identity and the collections it belongs to. This way, a single change reaches the whole affected set in one purge.
  • Keep one deliberately broad emergency tag. Fastly recommends a constant key such as all on every object, because purge-all cannot be run in soft mode. Akamai suggests the same pattern with a tag like ALL_CONTENT. Keep everyday purges narrow.
  • Verify the tags are actually attached rather than trusting a successful purge. Request with Fastly-Debug: 1 to see an object's surrogate keys. On Akamai, use the akamai-x-get-cache-tags pragma. Check for an X-Akamai-Cache-Tag-Error header reporting a tag too long, an illegal character, too many tags, or an oversized header.
  • When you change the tagging scheme, force a 200 refetch or purge the affected objects. A conditional revalidation will otherwise keep the old tags.
  • If you run more than one CDN, normalise separator, case and length in one place. This way the same purge pipeline cannot half-succeed on one provider.

Interactive Animation

Loading animation...

Examples

This code tags the responses in a Django app. Then it purges by tag with the Fastly API.

# Django middleware to add surrogate keys
class SurrogateKeyMiddleware:
    def process_response(self, request, response):
        if hasattr(request, '_surrogate_keys'):
            response['Surrogate-Key'] = ' '.join(request._surrogate_keys)
        return response

# In your view
def product_detail(request, product_id):
    product = Product.objects.get(id=product_id)
    request._surrogate_keys = [
        f'product-{product.id}',
        f'category-{product.category.slug}',
    ]
    return render(request, 'product.html', {'product': product})
# Purge by tag via Fastly API
curl -X POST https://api.fastly.com/service/SVC_ID/purge/product-123 \
  -H "Fastly-Key: YOUR_API_TOKEN"
# All URLs tagged with "product-123" are now invalidated

Frequently Asked Questions

A surrogate key, or cache tag, is a label the CDN indexes against a cached response alongside its cache key, normally set by the origin in a response header. Purging the label invalidates every object carrying it in one call. It cannot serve a request, only target content for purging.

This code tags the responses in a Django app. Then it purges by tag with the Fastly API.

# Django middleware to add surrogate keys
class SurrogateKeyMiddleware:
    def process_response(self, request, response):
        if hasattr(request, '_surrogate_keys'):
            response['Surrogate-Key'] = ' '.join(request._surrogate_keys)
        return response

# In your view
def product_detail(request, product_id):
    product = Product.objects.get(id=product_id)
    request._surrogate_keys = [
        f'product-{product.id}',
        f'category-{product.category.slug}',
    ]
    return render(request, 'product.html', {'product': product})
# Purge by tag via Fastly API
curl -X POST https://api.fastly.com/service/SVC_ID/purge/product-123 \
  -H "Fastly-Key: YOUR_API_TOKEN"
# All URLs tagged with "product-123" are now invalidated

Yes. Surrogate Key / Cache Tag is also known as cache tag. A surrogate key, or cache tag, is a label the CDN indexes against a cached response alongside its cache key, normally set by the origin in a response header. Purging the label invalidates every object carrying it in one call. It cannot serve a request, only target content for purging.