CloudFront Now Supports Tag-Based Cache Invalidation, So I Tried It Out

CloudFront Now Supports Tag-Based Cache Invalidation, So I Tried It Out

CloudFront now supports invalidation by cache tag. I built a test environment with an S3 origin and CloudFormation, and verified bulk invalidation via a shared tag and selective invalidation via a specific tag.
2026.04.30

This page has been translated by machine translation. View original

On April 29, 2026, Amazon CloudFront added support for Invalidation by cache tags.

https://aws.amazon.com/about-aws/whats-new/2026/04/cloudfront-invalidation-cache-tag/

With conventional CloudFront Invalidation, you could only specify URL paths individually, or use a wildcard (/*) to clear the cache over a broad range. With this update, you can attach tags to objects via origin response headers and then perform bulk Invalidation by specifying those tags.

I built a test environment with an S3 origin and CloudFormation, and checked the actual behavior.

How cache tag Invalidation works

Based on the official documentation, let's go over how it works.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/invalidation-by-tags.html

Get started in 3 steps

  1. Configure CacheTagConfig on the distribution: Specify the name of the HTTP header from which tags are read
  2. The origin attaches tags to the response headers: Multiple tags can be specified, separated by commas
  3. Invalidate with #tag-name: Specify it with the # prefix, like --paths "#version:v1"

With an S3 origin, you can use it just by setting tags in the object's metadata. S3 automatically returns it as an x-amz-meta-<key-name> header.

S3オブジェクトのメタデータ:
  cache-tag: "page:products, version:v1, category:electronics"
    ↓ S3が自動変換
レスポンスヘッダー:
  x-amz-meta-cache-tag: page:products, version:v1, category:electronics
    ↓ CloudFrontが読み取り
キャッシュタグとして保存: [page:products] [version:v1] [category:electronics]

Tag specifications

Item Limit
Maximum number of tags per object 50 (excess is ignored)
Maximum length of one tag 256 characters
Allowed characters ASCII 33–126 (spaces, commas, and control characters are not allowed)
Case sensitivity Case-insensitive

※ A tag value itself cannot contain commas or spaces. Commas are used as delimiters between tags, and spaces before and after the delimiters are trimmed during parsing. In other words, tag1,tag2 and tag1, tag2 are treated as equivalent.

Pricing

One tag = one path for billing purposes. The pricing structure is the same as conventional path Invalidation (the first 1,000 paths per month are free, then $0.005 per path).

The key point is that whether 100 or 10,000 objects are associated with a tag, one tag specification = one path charge.

https://aws.amazon.com/cloudfront/pricing/

Quotas

Item Limit
Rate of path or tag submissions 150 per second (paths and tags combined)
Wildcard Invalidation 1 per second
Number of files processed per tag Unlimited

Tags share the same quota as paths, and one tag counts as one path. For details, see the official quota page.

Building the test environment (CloudFormation)

We'll build the test environment with CloudFormation. CacheTagConfig is supported in CloudFormation.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachetagconfig.html

CloudFormation template
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFront Cache Tag Invalidation Test Stack

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'cf-cache-tag-test-${AWS::AccountId}'

  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowCloudFrontServicePrincipal
            Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: s3:GetObject
            Resource: !Sub '${S3Bucket.Arn}/*'
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}'

  OAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Sub 'cache-tag-test-oac-${AWS::AccountId}'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: Cache Tag Invalidation Test
        Enabled: true
        DefaultRootObject: index.html
        CacheTagConfig:
          HeaderName: x-amz-meta-cache-tag
        Origins:
          - Id: S3Origin
            DomainName: !GetAtt S3Bucket.RegionalDomainName
            OriginAccessControlId: !Ref OAC
            S3OriginConfig:
              OriginAccessIdentity: ''
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: redirect-to-https
          Compress: true
          ForwardedValues:
            QueryString: false
          DefaultTTL: 3600
          MinTTL: 180
          MaxTTL: 3600

Outputs:
  BucketName:
    Value: !Ref S3Bucket
  DistributionId:
    Value: !Ref Distribution
  DomainName:
    Value: !GetAtt Distribution.DomainName

The core setting of this update is CacheTagConfig.

    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        CacheTagConfig:
          HeaderName: x-amz-meta-cache-tag

The S3 metadata key cache-tag is returned as the x-amz-meta-cache-tag header, so that is what I specified for HeaderName.

For the cache settings, I used the legacy configuration (ForwardedValues) rather than the managed cache policy (CachingOptimized). During testing, with CachingOptimized, when the S3 origin did not return a Cache-Control header, CloudFront would query the origin with conditional requests, and caching behaved unstably. By setting MinTTL to 180 seconds with the legacy configuration and also attaching Cache-Control: max-age=300 on the S3 side, I was able to confirm stable caching behavior while respecting the origin's caching directives.

Deployment

aws cloudformation deploy \
  --stack-name cf-cache-tag-test \
  --template-file template.yaml \
  --region us-east-1

Uploading test content

Upload S3 objects with metadata (cache tags) and a Cache-Control header attached.

aws s3api put-object --bucket "$BUCKET" --key products.html \
  --body products.html --content-type text/html \
  --cache-control "max-age=300" \
  --metadata '{"cache-tag":"page:products, version:v1, category:electronics"}'

Tags are specified, separated by commas, in the cache-tag key of --metadata. The other files are uploaded in the same way.

Verification

We'll check the behavior with two test cases.

First, get the distribution ID from the CloudFormation Outputs.

DIST_ID=$(aws cloudformation describe-stacks \
  --stack-name cf-cache-tag-test \
  --query "Stacks[0].Outputs[?OutputKey=='DistributionId'].OutputValue" \
  --output text)

Test setup

File Tags
index.html page:home, version:v1
products.html page:products, version:v1, category:electronics
about.html page:about, version:v1

All three files share the version:v1 tag, and the tags starting with page: differ for each file.

TEST A: Bulk Invalidation with a common tag (#version:v1)

All three files have the version:v1 tag. After warming the cache by accessing each file several times, we run Invalidation specifying this tag.

aws cloudfront create-invalidation \
  --distribution-id "$DIST_ID" \
  --paths "#version:v1"

You simply specify a tag name with the # prefix instead of a path. Because # is interpreted as a comment by the shell, it must be enclosed in double quotes.

Results

File Before Invalidation After Invalidation
index.html Hit Miss ✅
products.html Hit Miss ✅
about.html Hit Miss ✅

※ X-Cache: Miss from cloudfront indicates that the cache was invalidated and the object was fetched again from the origin.

The cache of all files was invalidated. It took about 18 seconds from issuing the Invalidation to completion (the official documentation says under 25 seconds at P95).

TEST B: Selective Invalidation (#page:products)

Next, after warming the cache again, we run Invalidation specifying the page:products tag. Only products.html has this tag.

aws cloudfront create-invalidation \
  --distribution-id "$DIST_ID" \
  --paths "#page:products"

Results

File Before Invalidation After Invalidation
index.html Hit Hit ✅
products.html Hit Miss ✅
about.html Hit Hit ✅

Only products.html's cache was invalidated, while index.html and about.html remained cache hits. It took about 22 seconds from issuing to completion.

This is the decisive difference from conventional wildcards (/products/*). By grouping semantically with tags, you avoid clearing the cache of unrelated content.

Comparison with the conventional method

Conventional (path specification) Cache tags
How to specify /products/123.html or /products/* #brand:acme
Precision Depends on path structure. Easily catches unrelated content Invalidates only what's needed through semantic grouping
Operational burden Need to track and enumerate target URLs Once the app attaches tags, one request completes it
Cache hit ratio Tends to drop with wildcards Stays high because unrelated cache remains
Origin load Re-fetches occur for everything over-cleared Only minimal re-fetching is needed
Cost Enumerating individual URLs is billed per path. A wildcard is one path, but origin transfer costs increase One tag = one path charge. Flat regardless of the number of associated objects. Origin transfer costs are also minimal

The essential change brought by cache tags is that the decision of "what to clear" shifts from URL structure to application semantics (brand, version, user, etc.).

Tag design tips

The official documentation introduces the following example use cases.

Use case Example tags Example Invalidation
E-commerce product catalog category:electronics, brand:acme, product:12345 Invalidate all ACME products at once with #brand:acme
User-generated content user:12345, content-type:image Delete all content of a departed user at once with #user:12345
Content versioning version:v2, template:homepage Invalidate all v2 content at once on deployment with #version:v2

All of them are unified in the key:value format. Following this, if you establish rules for prefixes in your own project, you won't be confused during operation. Since multiple tags can be attached to one object, you can perform Invalidation across multiple axes.

Notes

  • Opt-in required: Tags are ignored on distributions where CacheTagConfig is not set
  • Be careful when changing the header name: If you change the header name in CacheTagConfig, tag Invalidation will no longer work on objects cached with the old header. A path Invalidation (/*) is needed before the change
  • Tag changes are not reflected in existing cache: Even if you change the tags in S3 metadata, the tags of objects already cached in CloudFront are not updated. To invalidate with a new tag, you first need to delete the existing cache with a path Invalidation and have responses with the new tags cached again
  • When CacheTagConfig is removed: Even after removal, existing cache continues to be served as usual until the TTL expires
  • Backward compatibility: Conventional path and wildcard Invalidation remain available
  • All variants are affected: When you invalidate by tag, all cache variants by cookie, header, and query string are also invalidated. You cannot leave only specific variants

Summary

CloudFront now supports Invalidation by cache tags, enabling flexible cache management that doesn't depend on URL structure.

In this verification, we set tags in S3 object metadata and confirmed that #version:v1 invalidated three files at once, and #page:products selectively invalidated just one file. You can avoid over-clearing the cache with wildcards, keep the cache hit ratio high, and precisely invalidate only the necessary content.

It also supports CloudFormation and can be used together with conventional path Invalidation, so it can be adopted gradually.

This time I tested with an S3 origin, but it is even more effective with a CMS origin that serves dynamic content.

For example, suppose you configure the CMS to return tags such as article:12345, category:tech, author:suzuki in the response headers when an article is updated. This lets you reflect bulk updates of articles in a specific category, or the replacement of profile information following a change in an author's affiliation, immediately with a single request such as #category:tech or #author:suzuki.

Because you can minimize the lag in content updates while keeping unrelated cache intact, it is expected to be useful in situations where you were struggling with cache strategy for a CMS and CloudFront combination.

Invalidation by author tags and category tags

I uploaded four articles to S3 and confirmed selective Invalidation by author tags and category tags.

Additional verification (TEST C / TEST D)

Upload four article pages with author and category tags attached.

File Tags
articles/001.html article:001, category:compute, author:suzuki
articles/002.html article:002, category:networking, author:suzuki
articles/003.html article:003, category:database, author:tanaka
articles/004.html article:004, category:storage, author:tanaka

TEST C: Bulk Invalidation by author tag (#author:suzuki)

aws cloudfront create-invalidation --distribution-id "$DIST_ID" --paths "#author:suzuki"
Article Author After Invalidation
001 (Intro to Lambda) suzuki Miss ✅
002 (CloudFront Design) suzuki Miss ✅
003 (DynamoDB in Practice) tanaka Hit ✅
004 (S3 Security) tanaka Hit ✅

Only suzuki's two articles were invalidated, while tanaka's articles remained cache hits.

TEST D: Selective Invalidation by category tag (#category:database)

aws cloudfront create-invalidation --distribution-id "$DIST_ID" --paths "#category:database"
Article Category After Invalidation
001 compute Hit ✅
002 networking Hit ✅
003 database Miss ✅
004 storage Hit ✅

For the same group of articles, we confirmed that we can invalidate only the targeted articles along either the author axis or the category axis.

Share this article

AWSのお困り事はクラスメソッドへ