AWS Mastery16 min read

    Amazon CloudFront Deep Dive: CDN, Caching, and Edge Computing on AWS

    Tarek Cheikh

    Founder & AWS Cloud Architect

    Amazon CloudFront Deep Dive: CDN, Caching, and Edge Computing on AWS

    CloudFront is the AWS content delivery network. It caches content at edge locations worldwide so users receive responses from the nearest location instead of the origin server. This reduces latency, offloads traffic from the origin, and includes DDoS protection through AWS Shield Standard at no extra cost.

    This article covers CloudFront from distribution creation to production patterns: S3 origins with Origin Access Control, cache policies, path-based routing, origin failover, CloudFront Functions, Lambda@Edge, WAF integration, signed URLs, cache invalidation, pricing, and monitoring.

    How CloudFront Works

    # CloudFront request flow:
    #
    # 1. User requests https://d111111abcdef8.cloudfront.net/image.jpg
    # 2. DNS resolves to the nearest CloudFront edge location
    # 3. Edge checks its local cache
    #    - HIT: return cached object immediately
    #    - MISS: forward to regional edge cache
    # 4. Regional edge cache checks its cache
    #    - HIT: return to edge, cache locally, serve to user
    #    - MISS: fetch from origin (S3, ALB, or custom HTTP server)
    # 5. Response cached at both regional edge cache and edge location
    # 6. Subsequent requests from nearby users hit the edge cache
    #
    # Infrastructure (2025):
    # - 600+ Points of Presence (PoPs) in 100+ cities, 50+ countries
    # - 13 regional edge caches (larger caches, longer retention)
    # - AWS global backbone network between edge and origin (not public internet)
    #
    # Two-tier caching:
    #
    #   [User] --> [Edge Location] --> [Regional Edge Cache] --> [Origin]
    #              450+ locations       13 locations              S3 / ALB / HTTP
    #              local cache          shared cache              your server

    Creating a Distribution with S3 Origin

    Origin Access Control (OAC) is the recommended method for S3 origins. It replaces the legacy Origin Access Identity (OAI) and supports SSE-KMS encrypted objects, all HTTP methods (including PUT and DELETE), and S3 Object Lambda.

    # Step 1: Create an Origin Access Control
    aws cloudfront create-origin-access-control \
        --origin-access-control-config '{
            "Name": "my-site-oac",
            "Description": "OAC for S3 static website",
            "SigningProtocol": "sigv4",
            "SigningBehavior": "always",
            "OriginAccessControlOriginType": "s3"
        }'
    
    # Note the Id from the response: OriginAccessControl.Id (e.g., E2QWRUHAPOMQZL)
    
    # Step 2: Create the distribution
    # Save the JSON below as distribution.json
    aws cloudfront create-distribution \
        --distribution-config file://distribution.json
    {
        "CallerReference": "my-site-2025-01",
        "Comment": "Static website with OAC",
        "Enabled": true,
        "DefaultRootObject": "index.html",
        "HttpVersion": "http2and3",
        "PriceClass": "PriceClass_All",
        "Origins": {
            "Quantity": 1,
            "Items": [
                {
                    "Id": "s3-origin",
                    "DomainName": "my-site-bucket.s3.us-east-1.amazonaws.com",
                    "OriginAccessControlId": "E2QWRUHAPOMQZL",
                    "S3OriginConfig": {
                        "OriginAccessIdentity": ""
                    }
                }
            ]
        },
        "DefaultCacheBehavior": {
            "TargetOriginId": "s3-origin",
            "ViewerProtocolPolicy": "redirect-to-https",
            "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
            "Compress": true,
            "AllowedMethods": {
                "Quantity": 2,
                "Items": ["GET", "HEAD"],
                "CachedMethods": {
                    "Quantity": 2,
                    "Items": ["GET", "HEAD"]
                }
            }
        },
        "ViewerCertificate": {
            "CloudFrontDefaultCertificate": true
        }
    }
    # CachePolicyId "658327ea-..." is the AWS managed CachingOptimized policy:
    #   MinTTL: 1s, DefaultTTL: 86400s (24h), MaxTTL: 31536000s (365 days)
    #   Gzip and Brotli compression enabled in the cache key
    #
    # S3OriginConfig with empty OriginAccessIdentity is required even with OAC.
    # This is an API requirement -- do not remove it.
    #
    # HttpVersion "http2and3" enables HTTP/2 and HTTP/3 (QUIC).
    # HTTP/3 reduces connection setup time, especially on mobile networks.
    
    # Step 3: Grant CloudFront access to the S3 bucket
    # Save as bucket-policy.json, replace the distribution ARN with yours
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowCloudFrontOAC",
                "Effect": "Allow",
                "Principal": {
                    "Service": "cloudfront.amazonaws.com"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::my-site-bucket/*",
                "Condition": {
                    "StringEquals": {
                        "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1A2B3C4D5E6F7"
                    }
                }
            }
        ]
    }
    aws s3api put-bucket-policy \
        --bucket my-site-bucket \
        --policy file://bucket-policy.json
    
    # The Condition block restricts access to YOUR specific distribution.
    # Without it, any CloudFront distribution could read your objects.
    
    # Verify the distribution is deployed (takes 5-10 minutes)
    aws cloudfront get-distribution --id E1A2B3C4D5E6F7 \
        --query 'Distribution.Status'
    # "Deployed"

    Custom Domain with ACM Certificate

    # CloudFront requires ACM certificates in us-east-1 regardless of
    # where your origin is located. CloudFront is a global service and
    # uses the us-east-1 region for certificate management.
    
    # Step 1: Request a certificate in us-east-1
    aws acm request-certificate \
        --domain-name cdn.example.com \
        --subject-alternative-names "*.example.com" \
        --validation-method DNS \
        --region us-east-1
    
    # Step 2: Complete DNS validation (add the CNAME record ACM provides)
    aws acm describe-certificate \
        --certificate-arn arn:aws:acm:us-east-1:123456789012:certificate/abc-123 \
        --region us-east-1 \
        --query 'Certificate.DomainValidationOptions[0].ResourceRecord'
    
    # Step 3: Update the distribution with the custom domain
    # Get the current config and ETag
    aws cloudfront get-distribution-config --id E1A2B3C4D5E6F7 > dist-config.json
    
    # Edit dist-config.json:
    # - Replace ViewerCertificate with:
    #     "ViewerCertificate": {
    #         "ACMCertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/abc-123",
    #         "SSLSupportMethod": "sni-only",
    #         "MinimumProtocolVersion": "TLSv1.2_2021"
    #     }
    # - Add Aliases:
    #     "Aliases": {"Quantity": 1, "Items": ["cdn.example.com"]}
    
    # Apply the update (use the ETag from the get-distribution-config response)
    aws cloudfront update-distribution \
        --id E1A2B3C4D5E6F7 \
        --if-match E2QWRUHEXAMPLE \
        --distribution-config file://dist-config-updated.json
    
    # SSLSupportMethod "sni-only" uses Server Name Indication (free).
    # The alternative "vip" dedicates an IP address ($600/month).
    # MinimumProtocolVersion "TLSv1.2_2021" enforces TLS 1.2 with modern ciphers.
    
    # Step 4: Create a DNS CNAME record
    # cdn.example.com CNAME d111111abcdef8.cloudfront.net

    Cache Policies

    # Cache policies control what goes into the cache key and how long
    # objects are cached. AWS provides managed policies for common patterns:
    #
    # Managed Cache Policies:
    # Name                             ID                                     Use Case
    # -------------------------------------------------------------------------------------------------
    # CachingOptimized                 658327ea-f89d-4fab-a63d-7e88639e58f6   S3, static assets
    # CachingDisabled                  4135ea2d-6df8-44a3-9df3-4b5a84be39ad   APIs, dynamic content
    # CachingOptimizedForUncompressed  b2884449-e4de-46a7-ac36-70bc7f1ddd6d   Pre-compressed content
    #
    # List all managed policies:
    aws cloudfront list-cache-policies --type managed \
        --query 'CachePolicyList.Items[].CachePolicy.{Name:CachePolicyConfig.Name,Id:Id}'
    
    # Create a custom cache policy (cache API responses by query string)
    aws cloudfront create-cache-policy --cache-policy-config '{
        "Name": "CustomAPI",
        "Comment": "Cache API responses by path and query strings",
        "DefaultTTL": 300,
        "MaxTTL": 3600,
        "MinTTL": 0,
        "ParametersInCacheKeyAndForwardedToOrigin": {
            "EnableAcceptEncodingGzip": true,
            "EnableAcceptEncodingBrotli": true,
            "HeadersConfig": {"HeaderBehavior": "none"},
            "CookiesConfig": {"CookieBehavior": "none"},
            "QueryStringsConfig": {
                "QueryStringBehavior": "whitelist",
                "QueryStrings": {"Quantity": 2, "Items": ["page", "limit"]}
            }
        }
    }'
    
    # The cache key determines when CloudFront serves a cached response
    # vs fetching from the origin. Including more parameters (headers,
    # cookies, query strings) in the cache key means more cache misses.
    # Include ONLY what actually changes the response.

    Multiple Origins and Path-Based Routing

    # Route different URL paths to different origins:
    #
    #   /api/*     --> ALB (not cached, all headers forwarded)
    #   /*         --> S3 (cached with CachingOptimized, default)
    #
    # In the distribution config, define multiple origins:
    # "Origins": {
    #     "Quantity": 2,
    #     "Items": [
    #         {
    #             "Id": "s3-origin",
    #             "DomainName": "my-site-bucket.s3.us-east-1.amazonaws.com",
    #             "OriginAccessControlId": "E2QWRUHAPOMQZL",
    #             "S3OriginConfig": {"OriginAccessIdentity": ""}
    #         },
    #         {
    #             "Id": "alb-origin",
    #             "DomainName": "my-alb-123456.us-east-1.elb.amazonaws.com",
    #             "CustomOriginConfig": {
    #                 "HTTPPort": 80,
    #                 "HTTPSPort": 443,
    #                 "OriginProtocolPolicy": "https-only",
    #                 "OriginSslProtocols": {"Quantity": 1, "Items": ["TLSv1.2"]}
    #             }
    #         }
    #     ]
    # }
    #
    # Add a cache behavior for the API path:
    # "CacheBehaviors": {
    #     "Quantity": 1,
    #     "Items": [{
    #         "PathPattern": "/api/*",
    #         "TargetOriginId": "alb-origin",
    #         "ViewerProtocolPolicy": "https-only",
    #         "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
    #         "OriginRequestPolicyId": "216adef6-5c7f-47e4-b989-5492eafa07d3",
    #         "AllowedMethods": {
    #             "Quantity": 7,
    #             "Items": ["GET","HEAD","OPTIONS","PUT","POST","PATCH","DELETE"],
    #             "CachedMethods": {"Quantity": 2, "Items": ["GET","HEAD"]}
    #         }
    #     }]
    # }
    #
    # CachingDisabled (4135ea2d...) forwards every request to the origin.
    # AllViewer origin request policy (216adef6...) passes all viewer headers
    # to the ALB so it receives the full request context.
    # DefaultCacheBehavior (/*) handles everything else with S3 + CachingOptimized.

    Origin Failover

    # Origin groups provide automatic failover. If the primary origin returns
    # a 5xx error or times out, CloudFront retries against the secondary origin.
    #
    # "OriginGroups": {
    #     "Quantity": 1,
    #     "Items": [{
    #         "Id": "s3-failover-group",
    #         "FailoverCriteria": {
    #             "StatusCodes": {"Quantity": 4, "Items": [500, 502, 503, 504]}
    #         },
    #         "Members": {
    #             "Quantity": 2,
    #             "Items": [
    #                 {"OriginId": "s3-primary-us-east-1"},
    #                 {"OriginId": "s3-secondary-eu-west-1"}
    #             ]
    #         }
    #     }]
    # }
    #
    # Both origins must be defined in the Origins section.
    # The DefaultCacheBehavior targets the origin GROUP:
    #   "TargetOriginId": "s3-failover-group"
    #
    # Common use case: S3 buckets in different regions with
    # cross-region replication enabled for disaster recovery.

    CloudFront Functions

    # CloudFront Functions run lightweight JavaScript at edge locations.
    # - Sub-millisecond execution
    # - Viewer-request and viewer-response events only
    # - 10 KB max package size, 2 MB max memory
    # - JavaScript (cloudfront-js-2.0 runtime)
    # - $0.10 per million invocations (2 million/month always free)
    #
    # Use cases: URL rewrites, redirects, header manipulation, simple auth
    // spa-rewrite.js -- rewrite SPA paths to index.html
    function handler(event) {
        var request = event.request;
        var uri = request.uri;
    
        if (uri.endsWith('/')) {
            request.uri += 'index.html';
        } else if (!uri.includes('.')) {
            request.uri += '/index.html';
        }
    
        return request;
    }
    # Create the function
    aws cloudfront create-function \
        --name spa-url-rewrite \
        --function-config '{"Comment":"Rewrite SPA paths","Runtime":"cloudfront-js-2.0"}' \
        --function-code fileb://spa-rewrite.js
    
    # Publish the function (functions are created in DEVELOPMENT stage)
    aws cloudfront publish-function \
        --name spa-url-rewrite \
        --if-match ETVPDKIKX0DER
    
    # Associate with a cache behavior in the distribution config:
    # "FunctionAssociations": {
    #     "Quantity": 1,
    #     "Items": [{
    #         "FunctionARN": "arn:aws:cloudfront::123456789012:function/spa-url-rewrite",
    #         "EventType": "viewer-request"
    #     }]
    # }

    Lambda@Edge

    # Lambda@Edge runs full Lambda functions at CloudFront edge locations.
    # - Viewer triggers: 5s timeout, 128 MB memory
    # - Origin triggers: 30s timeout, up to 10,240 MB memory
    # - Supports Node.js 20.x and Python 3.12
    # - MUST be created in us-east-1 (replicated to edges automatically)
    # - $0.60 per million requests + duration charges
    #
    # Use cases: advanced auth, A/B testing, dynamic content generation,
    #            image transformation, security headers
    // security-headers/index.mjs -- deploy to us-east-1 as origin-response trigger
    export const handler = async (event) => {
        const response = event.Records[0].cf.response;
        const headers = response.headers;
    
        headers['strict-transport-security'] = [{
            key: 'Strict-Transport-Security',
            value: 'max-age=63072000; includeSubDomains; preload'
        }];
    
        headers['x-content-type-options'] = [{
            key: 'X-Content-Type-Options',
            value: 'nosniff'
        }];
    
        headers['x-frame-options'] = [{
            key: 'X-Frame-Options',
            value: 'DENY'
        }];
    
        headers['referrer-policy'] = [{
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin'
        }];
    
        return response;
    };
    # Alternatively, use the managed SecurityHeadersPolicy response headers policy
    # (67f7725c-6f97-4210-82d7-5512b31e9d03) to add security headers without code.
    # Add "ResponseHeadersPolicyId" to the cache behavior in your distribution config.
    #
    # CloudFront Functions vs Lambda@Edge:
    #
    # Feature               CloudFront Functions     Lambda@Edge
    # -------------------------------------------------------------------
    # Runtime               JavaScript only          Node.js, Python
    # Execution time        Sub-millisecond          5s (viewer) / 30s (origin)
    # Memory                2 MB                     128 MB - 10,240 MB
    # Network access        Yes (cloudfront-js-2.0)  Yes
    # Event types           Viewer only              Viewer + Origin
    # Pricing               $0.10/million            $0.60/million + duration
    # Package size          10 KB                    50 MB (250 MB with layers)
    # Deploy region         Global (automatic)       us-east-1 (replicated)
    #
    # Use CloudFront Functions for simple request/response manipulation.
    # Use Lambda@Edge for origin triggers, longer execution, or Python.

    Security

    WAF Integration

    # Associate a WAF web ACL with CloudFront to block malicious requests.
    # WAF for CloudFront MUST be created in us-east-1.
    
    aws wafv2 create-web-acl \
        --name cloudfront-protection \
        --scope CLOUDFRONT \
        --region us-east-1 \
        --default-action '{"Allow":{}}' \
        --rules '[
            {
                "Name": "AWSManagedCommonRuleSet",
                "Priority": 1,
                "Statement": {
                    "ManagedRuleGroupStatement": {
                        "VendorName": "AWS",
                        "Name": "AWSManagedRulesCommonRuleSet"
                    }
                },
                "OverrideAction": {"None":{}},
                "VisibilityConfig": {
                    "SampledRequestsEnabled": true,
                    "CloudWatchMetricsEnabled": true,
                    "MetricName": "CommonRuleSet"
                }
            },
            {
                "Name": "RateLimit",
                "Priority": 2,
                "Statement": {
                    "RateBasedStatement": {
                        "Limit": 2000,
                        "AggregateKeyType": "IP"
                    }
                },
                "Action": {"Block":{}},
                "VisibilityConfig": {
                    "SampledRequestsEnabled": true,
                    "CloudWatchMetricsEnabled": true,
                    "MetricName": "RateLimit"
                }
            }
        ]' \
        --visibility-config '{
            "SampledRequestsEnabled": true,
            "CloudWatchMetricsEnabled": true,
            "MetricName": "cloudfront-waf"
        }'
    
    # RateBasedStatement Limit: max requests in any 5-minute window per IP.
    # 2000 = roughly 400 requests/minute per IP before blocking.

    Geo Restriction

    # Block or allow access by country using ISO 3166-1 alpha-2 codes.
    # Add to the distribution config:
    #
    #   "Restrictions": {
    #       "GeoRestriction": {
    #           "RestrictionType": "whitelist",
    #           "Quantity": 3,
    #           "Items": ["US", "CA", "GB"]
    #       }
    #   }
    #
    # RestrictionType: "whitelist" (allow only listed countries),
    # "blacklist" (block listed countries), or "none" (no restriction).
    # CloudFront uses a GeoIP database to determine the viewer's country.

    Signed URLs

    # Signed URLs restrict access to specific objects for a limited time.
    # Use for premium content, temporary downloads, or time-limited access.
    
    # Step 1: Create a public key
    aws cloudfront create-public-key --public-key-config '{
        "CallerReference": "signing-key-2025",
        "Name": "content-signing-key",
        "EncodedKey": "-----BEGIN PUBLIC KEY-----
    MIIBI...
    -----END PUBLIC KEY-----"
    }'
    
    # Step 2: Create a key group
    aws cloudfront create-key-group --key-group-config '{
        "Name": "content-signers",
        "Items": ["K1A2B3C4D5E6F7"],
        "Comment": "Key group for signed URLs"
    }'
    
    # Step 3: Add TrustedKeyGroups to the cache behavior:
    #   "TrustedKeyGroups": {
    #       "Enabled": true,
    #       "Quantity": 1,
    #       "Items": ["a1b2c3d4-key-group-id"]
    #   }
    
    # Step 4: Generate a signed URL
    aws cloudfront sign \
        --url https://d111111abcdef8.cloudfront.net/premium/video.mp4 \
        --key-pair-id K1A2B3C4D5E6F7 \
        --private-key file://private-key.pem \
        --date-less-than "2025-07-01T00:00:00Z"
    
    # The output is a URL with a signature that expires at the specified date.
    # Unsigned requests are rejected with 403.

    Cache Invalidation

    # Invalidation removes objects from edge caches before their TTL expires.
    
    # Invalidate specific paths
    aws cloudfront create-invalidation \
        --distribution-id E1A2B3C4D5E6F7 \
        --paths '/index.html' '/css/main.css' '/js/app.js'
    
    # Invalidate everything
    aws cloudfront create-invalidation \
        --distribution-id E1A2B3C4D5E6F7 \
        --paths '/*'
    
    # Check invalidation status
    aws cloudfront get-invalidation \
        --distribution-id E1A2B3C4D5E6F7 \
        --id I1A2B3C4D5E6F7
    
    # Pricing:
    # - First 1,000 invalidation paths per month: free
    # - Each additional path: $0.005
    # - Wildcard /* counts as 1 path
    # - Invalidation propagates to all edge locations in 5-15 minutes
    #
    # Best practice: use versioned file names (main.abc123.js) instead of
    # invalidation. A new file name is a different cache key, so the update
    # is immediate with no invalidation delay or cost. Reserve invalidation
    # for index.html and other files that cannot be versioned.

    Origin Shield, Compression, and HTTP/3

    # Origin Shield adds a third caching layer between regional edge caches
    # and your origin. All cache misses go through Origin Shield first,
    # collapsing duplicate origin requests.
    #
    # Without Origin Shield:
    #   [Edge] --> [Regional 1] --> [Origin]
    #   [Edge] --> [Regional 2] --> [Origin]  (same object, two origin requests)
    #
    # With Origin Shield:
    #   [Edge] --> [Regional 1] --> [Origin Shield] --> [Origin]
    #   [Edge] --> [Regional 2] --> [Origin Shield]    (cache hit at Shield)
    #
    # Enable in the origin config:
    #   "OriginShield": {
    #       "Enabled": true,
    #       "OriginShieldRegion": "us-east-1"
    #   }
    #
    # Choose the region closest to your origin.
    # Additional cost: $0.0090 per 10,000 requests (US/Europe).
    # Worth it for high-traffic distributions or expensive origin compute.
    
    # Compression:
    # CloudFront compresses automatically when:
    # 1. Cache behavior has Compress: true
    # 2. Viewer sends Accept-Encoding: gzip or br
    # 3. Object is between 1,000 bytes and 10 MB
    # 4. Content-Type is compressible (HTML, CSS, JS, JSON, XML, SVG)
    #
    # CachingOptimized policy includes gzip and Brotli in the cache key,
    # so compressed and uncompressed versions are cached separately.
    # Brotli achieves 15-25% better compression than gzip for text content.
    
    # HTTP/3 (QUIC):
    # Enabled with HttpVersion: "http2and3" in the distribution config.
    # Reduces connection setup time compared to HTTP/2 (fewer round trips).
    # Handles network transitions (Wi-Fi to cellular) without dropping connections.

    Price Classes and Pricing

    # Price classes trade global coverage for lower costs:
    #
    # PriceClass_All:   All edge locations worldwide (best performance, default)
    # PriceClass_200:   US, Canada, Europe, Asia, Middle East, Africa
    # PriceClass_100:   US, Canada, Europe (lowest cost)
    #
    # Users outside included regions still access content, but CloudFront
    # routes them to the nearest INCLUDED edge location (higher latency).
    
    # Pricing (us-east-1, 2025):
    #
    # Data transfer out to internet:
    #   First 10 TB/month:   $0.085/GB
    #   Next 40 TB:          $0.080/GB
    #   Next 100 TB:         $0.060/GB
    #   Next 350 TB:         $0.040/GB
    #
    # Requests (US/Europe):
    #   HTTP:   $0.0075 per 10,000 ($0.75 per million)
    #   HTTPS:  $0.0100 per 10,000 ($1.00 per million)
    #
    # Free tier (always free, not limited to 12 months):
    #   1 TB data transfer out per month
    #   10,000,000 HTTP/HTTPS requests per month
    #   2,000,000 CloudFront Functions invocations per month
    #
    # Origin Shield:          $0.0090 per 10,000 requests (US/Europe)
    # Lambda@Edge:            $0.60 per million requests + duration
    # CloudFront Functions:   $0.10 per million invocations
    # Invalidation:           first 1,000 paths/month free, $0.005/path after
    #
    # S3 origin data transfer to CloudFront: free.
    # Custom origin data transfer to CloudFront: standard data transfer rates.
    #
    # Example: 2 TB transfer, 50 million HTTPS requests/month
    #   Data: (1 TB free) + 1 TB * $0.085/GB * 1024 GB = $87.04
    #   Requests: (10M free) + 40M * $1.00/M = $40.00
    #   Total: ~$127/month

    Monitoring

    # CloudFront metrics in CloudWatch (all reported in us-east-1):
    #
    # Requests              -- total viewer requests
    # BytesDownloaded       -- data transferred to viewers
    # BytesUploaded         -- data from viewers (POST/PUT)
    # TotalErrorRate        -- percentage of 4xx and 5xx responses
    # 4xxErrorRate          -- percentage of 4xx responses
    # 5xxErrorRate          -- percentage of 5xx responses
    # CacheHitRate          -- percentage of requests served from cache
    # OriginLatency         -- time for CloudFront to get a response from origin
    
    # Alarm: cache hit rate dropped below 80%
    aws cloudwatch put-metric-alarm \
        --alarm-name cloudfront-cache-hit-low \
        --namespace AWS/CloudFront \
        --metric-name CacheHitRate \
        --dimensions Name=DistributionId,Value=E1A2B3C4D5E6F7 Name=Region,Value=Global \
        --statistic Average \
        --period 3600 \
        --evaluation-periods 3 \
        --threshold 80 \
        --comparison-operator LessThanThreshold \
        --alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts \
        --region us-east-1
    
    # Alarm: 5xx error rate above 5%
    aws cloudwatch put-metric-alarm \
        --alarm-name cloudfront-5xx-high \
        --namespace AWS/CloudFront \
        --metric-name 5xxErrorRate \
        --dimensions Name=DistributionId,Value=E1A2B3C4D5E6F7 Name=Region,Value=Global \
        --statistic Average \
        --period 300 \
        --evaluation-periods 2 \
        --threshold 5 \
        --comparison-operator GreaterThanThreshold \
        --alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts \
        --region us-east-1
    
    # Access logs: delivered to S3, free, approximately 1-hour delay.
    # Enable in the distribution config:
    #   "Logging": {
    #       "Enabled": true,
    #       "IncludeCookies": false,
    #       "Bucket": "my-cloudfront-logs.s3.amazonaws.com",
    #       "Prefix": "cdn/"
    #   }
    #
    # Real-time logs: delivered to Kinesis Data Streams with sub-minute latency.
    # Use for real-time dashboards and alerting.

    Best Practices

    Caching

    • Use versioned file names for static assets (main.abc123.js) instead of cache invalidation. A new name is a different cache key with immediate effect and no cost.
    • Set long TTLs (1 year) for versioned assets and short TTLs (5 minutes to 1 hour) for index.html and non-versioned files.
    • Use the CachingOptimized managed cache policy for S3 origins and CachingDisabled for API origins.
    • Monitor CacheHitRate. A healthy distribution should achieve 85-95%. If it drops, check whether the cache key includes unnecessary headers, cookies, or query strings.
    • Enable Origin Shield for high-traffic distributions to reduce origin load and improve cache hit rate across regions.

    Security

    • Use OAC (Origin Access Control) for S3 origins, not OAI. OAC supports SSE-KMS, all HTTP methods, and S3 Object Lambda.
    • Enforce HTTPS with ViewerProtocolPolicy: redirect-to-https and set MinimumProtocolVersion: TLSv1.2_2021.
    • Attach a WAF web ACL with AWS managed rules and a rate-based rule to block volumetric attacks.
    • Use the SecurityHeadersPolicy managed response headers policy to add HSTS, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy without code.
    • Restrict S3 bucket access with a Condition on AWS:SourceArn matching your specific distribution ARN.

    Performance

    • Enable HTTP/3 (HttpVersion: http2and3) for faster connection setup, especially on mobile networks.
    • Enable compression (Compress: true) with CachingOptimized to serve gzip and Brotli automatically.
    • Use CloudFront Functions ($0.10/million) for simple edge logic instead of Lambda@Edge ($0.60/million) when you only need viewer-request/response triggers.
    • Choose the price class that matches your audience. PriceClass_100 is sufficient if users are in US, Canada, and Europe.

    Go Deeper: The State of AWS Security 2026

    This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.

    AWSCloudFrontCDNEdge ComputingPerformanceSecurity