Skip to main content
Back to Blog
20 September 202513 min read

AWS CloudFront: CDN and Edge Computing

AWSCloudFrontCDNEdge ComputingPerformance

Accelerating content delivery with AWS CloudFront. Edge locations, caching strategies, Lambda@Edge, CloudFront Functions, and security best practices.


AWS CloudFront: CDN and Edge Computing

AWS CloudFront is a global content delivery network that accelerates delivery of websites, APIs, and other web content. With edge locations worldwide and integration with Lambda@Edge and CloudFront Functions, it enables both content caching and edge computing capabilities.

CloudFront Fundamentals

Architecture Overview

CloudFront Architecture:

    Users → Edge Location → Regional Edge Cache → Origin

    ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
    │   Edge      │────▶│  Regional   │────▶│   Origin    │
    │  Location   │     │    Edge     │     │   (S3/ALB)  │
    │  (400+)     │     │   Cache     │     │             │
    └─────────────┘     └─────────────┘     └─────────────┘
           │                  │
           ▼                  ▼
    CloudFront Functions   Lambda@Edge
    (viewer request/       (origin/viewer
     response)              request/response)

Key Concepts

ConceptDescription
DistributionConfiguration for content delivery
OriginSource of content (S3, ALB, custom)
BehaviourRules for how requests are handled
Cache PolicyRules for caching content
TTLTime to live for cached content
InvalidationForcing cache refresh

Distribution Configuration

Static Website with S3 Origin

# cloudfront.tf resource "aws_cloudfront_distribution" "website" { enabled = true is_ipv6_enabled = true comment = "${var.project} website distribution" default_root_object = "index.html" aliases = [var.domain_name, "www.${var.domain_name}"] price_class = "PriceClass_100" # North America and Europe origin { domain_name = aws_s3_bucket.website.bucket_regional_domain_name origin_id = "S3-${aws_s3_bucket.website.id}" origin_access_control_id = aws_cloudfront_origin_access_control.website.id } default_cache_behavior { allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] target_origin_id = "S3-${aws_s3_bucket.website.id}" viewer_protocol_policy = "redirect-to-https" compress = true cache_policy_id = aws_cloudfront_cache_policy.static.id origin_request_policy_id = aws_cloudfront_origin_request_policy.static.id function_association { event_type = "viewer-request" function_arn = aws_cloudfront_function.url_rewrite.arn } } # Custom error pages for SPA custom_error_response { error_code = 404 response_code = 200 response_page_path = "/index.html" error_caching_min_ttl = 300 } custom_error_response { error_code = 403 response_code = 200 response_page_path = "/index.html" error_caching_min_ttl = 300 } restrictions { geo_restriction { restriction_type = "none" } } viewer_certificate { acm_certificate_arn = aws_acm_certificate.website.arn ssl_support_method = "sni-only" minimum_protocol_version = "TLSv1.2_2021" } tags = var.tags } resource "aws_cloudfront_origin_access_control" "website" { name = "${var.project}-oac" description = "OAC for ${var.project} website" origin_access_control_origin_type = "s3" signing_behavior = "always" signing_protocol = "sigv4" } # S3 bucket policy for CloudFront access resource "aws_s3_bucket_policy" "website" { bucket = aws_s3_bucket.website.id policy = jsonencode({ Version = "2012-10-17" Statement = [{ Sid = "AllowCloudFrontServicePrincipal" Effect = "Allow" Principal = { Service = "cloudfront.amazonaws.com" } Action = "s3:GetObject" Resource = "${aws_s3_bucket.website.arn}/*" Condition = { StringEquals = { "AWS:SourceArn" = aws_cloudfront_distribution.website.arn } } }] }) }

API with ALB Origin

# api-distribution.tf resource "aws_cloudfront_distribution" "api" { enabled = true is_ipv6_enabled = true comment = "${var.project} API distribution" aliases = ["api.${var.domain_name}"] origin { domain_name = aws_lb.api.dns_name origin_id = "ALB-${aws_lb.api.name}" custom_origin_config { http_port = 80 https_port = 443 origin_protocol_policy = "https-only" origin_ssl_protocols = ["TLSv1.2"] } custom_header { name = "X-CloudFront-Secret" value = var.cloudfront_secret } } default_cache_behavior { allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] cached_methods = ["GET", "HEAD", "OPTIONS"] target_origin_id = "ALB-${aws_lb.api.name}" viewer_protocol_policy = "https-only" compress = true cache_policy_id = aws_cloudfront_cache_policy.api.id origin_request_policy_id = aws_cloudfront_origin_request_policy.api.id response_headers_policy_id = aws_cloudfront_response_headers_policy.security.id } viewer_certificate { acm_certificate_arn = aws_acm_certificate.api.arn ssl_support_method = "sni-only" minimum_protocol_version = "TLSv1.2_2021" } restrictions { geo_restriction { restriction_type = "none" } } web_acl_id = aws_wafv2_web_acl.api.arn tags = var.tags }

Cache Policies

Custom Cache Policies

# cache-policies.tf resource "aws_cloudfront_cache_policy" "static" { name = "${var.project}-static-cache" comment = "Cache policy for static assets" default_ttl = 86400 # 1 day max_ttl = 31536000 # 1 year min_ttl = 0 parameters_in_cache_key_and_forwarded_to_origin { cookies_config { cookie_behavior = "none" } headers_config { header_behavior = "none" } query_strings_config { query_string_behavior = "none" } enable_accept_encoding_brotli = true enable_accept_encoding_gzip = true } } resource "aws_cloudfront_cache_policy" "api" { name = "${var.project}-api-cache" comment = "Cache policy for API responses" default_ttl = 0 max_ttl = 3600 min_ttl = 0 parameters_in_cache_key_and_forwarded_to_origin { cookies_config { cookie_behavior = "none" } headers_config { header_behavior = "whitelist" headers { items = ["Authorization", "Accept"] } } query_strings_config { query_string_behavior = "all" } } } resource "aws_cloudfront_origin_request_policy" "api" { name = "${var.project}-api-origin" comment = "Origin request policy for API" cookies_config { cookie_behavior = "all" } headers_config { header_behavior = "allViewer" } query_strings_config { query_string_behavior = "all" } }

CloudFront Functions

URL Rewriting

// url-rewrite.js (CloudFront Function) function handler(event) { var request = event.request; var uri = request.uri; // Add index.html to directory requests if (uri.endsWith('/')) { request.uri += 'index.html'; } // Add .html extension for clean URLs else if (!uri.includes('.')) { request.uri += '/index.html'; } return request; }

Security Headers

// security-headers.js (CloudFront Function) function handler(event) { var response = event.response; var headers = response.headers; // Security headers headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload' }; headers['x-content-type-options'] = { value: 'nosniff' }; headers['x-frame-options'] = { value: 'DENY' }; headers['x-xss-protection'] = { value: '1; mode=block' }; headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin' }; headers['content-security-policy'] = { value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.example.com" }; return response; }

Terraform Configuration for Functions

# functions.tf resource "aws_cloudfront_function" "url_rewrite" { name = "${var.project}-url-rewrite" runtime = "cloudfront-js-2.0" comment = "URL rewriting for SPA" publish = true code = file("${path.module}/functions/url-rewrite.js") } resource "aws_cloudfront_function" "security_headers" { name = "${var.project}-security-headers" runtime = "cloudfront-js-2.0" comment = "Add security headers" publish = true code = file("${path.module}/functions/security-headers.js") }

Lambda@Edge

A/B Testing at the Edge

// ab-test.ts (Lambda@Edge) import { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda'; const EXPERIMENT_COOKIE = 'experiment_variant'; const VARIANTS = ['A', 'B']; const VARIANT_WEIGHTS = [50, 50]; // 50% each export const handler = async ( event: CloudFrontRequestEvent ): Promise<CloudFrontRequestResult> => { const request = event.Records[0].cf.request; const headers = request.headers; // Check for existing variant cookie let variant = getVariantFromCookie(headers); if (!variant) { // Assign new variant variant = assignVariant(); // Add cookie to request for origin const cookieValue = `${EXPERIMENT_COOKIE}=${variant}; Path=/; Secure; SameSite=Lax`; headers.cookie = headers.cookie || []; headers.cookie.push({ key: 'Cookie', value: cookieValue }); } // Route to variant-specific origin or path if (variant === 'B') { request.origin = { s3: { domainName: 'variant-b-bucket.s3.amazonaws.com', region: 'eu-west-1', authMethod: 'origin-access-identity', path: '', customHeaders: {} } }; request.headers.host = [{ key: 'Host', value: 'variant-b-bucket.s3.amazonaws.com' }]; } return request; }; const getVariantFromCookie = ( headers: Record<string, Array<{ key?: string; value: string }>> ): string | null => { const cookies = headers.cookie?.[0]?.value || ''; const match = cookies.match(new RegExp(`${EXPERIMENT_COOKIE}=([AB])`)); return match ? match[1] : null; }; const assignVariant = (): string => { const random = Math.random() * 100; let cumulative = 0; for (let i = 0; i < VARIANTS.length; i++) { cumulative += VARIANT_WEIGHTS[i]; if (random <= cumulative) { return VARIANTS[i]; } } return VARIANTS[0]; };

Image Optimisation

// image-resize.ts (Lambda@Edge - Origin Response) import { CloudFrontResponseEvent, CloudFrontResponseResult } from 'aws-lambda'; import sharp from 'sharp'; import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; const s3 = new S3Client({ region: 'eu-west-1' }); export const handler = async ( event: CloudFrontResponseEvent ): Promise<CloudFrontResponseResult> => { const response = event.Records[0].cf.response; const request = event.Records[0].cf.request; // Only process images if (!request.uri.match(/\.(jpg|jpeg|png|webp)$/i)) { return response; } // Check for resize parameters const params = new URLSearchParams(request.querystring); const width = parseInt(params.get('w') || '0'); const quality = parseInt(params.get('q') || '80'); if (!width || response.status !== '200') { return response; } try { // Get original image from S3 const bucket = request.origin?.s3?.domainName.split('.')[0]; const key = request.uri.substring(1); const s3Response = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key })); const originalImage = await s3Response.Body?.transformToByteArray(); if (!originalImage) { return response; } // Resize image const resizedImage = await sharp(originalImage) .resize(width, null, { withoutEnlargement: true }) .webp({ quality }) .toBuffer(); // Return resized image response.status = '200'; response.statusDescription = 'OK'; response.body = resizedImage.toString('base64'); response.bodyEncoding = 'base64'; response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/webp' }]; response.headers['cache-control'] = [{ key: 'Cache-Control', value: 'public, max-age=31536000' }]; return response; } catch (error) { console.error('Image resize error:', error); return response; } };

Security Best Practices

WAF Integration

# waf.tf resource "aws_wafv2_web_acl" "cloudfront" { name = "${var.project}-cloudfront-waf" description = "WAF for CloudFront distribution" scope = "CLOUDFRONT" provider = aws.us-east-1 # WAF for CloudFront must be in us-east-1 default_action { allow {} } # AWS Managed Rules - Common Rule Set rule { name = "AWSManagedRulesCommonRuleSet" priority = 1 override_action { none {} } statement { managed_rule_group_statement { name = "AWSManagedRulesCommonRuleSet" vendor_name = "AWS" } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "AWSManagedRulesCommonRuleSetMetric" sampled_requests_enabled = true } } # Rate limiting rule { name = "RateLimitRule" priority = 2 action { block {} } statement { rate_based_statement { limit = 2000 aggregate_key_type = "IP" } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "RateLimitRuleMetric" sampled_requests_enabled = true } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "CloudFrontWAFMetric" sampled_requests_enabled = true } }

Monitoring

CloudWatch Dashboard

# monitoring.tf resource "aws_cloudwatch_dashboard" "cloudfront" { dashboard_name = "${var.project}-cloudfront" dashboard_body = jsonencode({ widgets = [ { type = "metric" x = 0 y = 0 width = 12 height = 6 properties = { metrics = [ ["AWS/CloudFront", "Requests", "DistributionId", aws_cloudfront_distribution.website.id, "Region", "Global"], [".", "BytesDownloaded", ".", ".", ".", "."] ] title = "Requests and Bytes" region = "us-east-1" } }, { type = "metric" x = 12 y = 0 width = 12 height = 6 properties = { metrics = [ ["AWS/CloudFront", "CacheHitRate", "DistributionId", aws_cloudfront_distribution.website.id, "Region", "Global"] ] title = "Cache Hit Rate" region = "us-east-1" } } ] }) }

Key Takeaways

  1. Use Origin Access Control: Secure S3 origins with OAC instead of OAI

  2. Cache strategically: Different content types need different cache policies

  3. Compress content: Enable Brotli and Gzip compression for faster delivery

  4. Security headers: Use CloudFront Functions or response headers policies

  5. WAF integration: Protect against common attacks at the edge

  6. Invalidate wisely: Use versioned file names instead of frequent invalidations

  7. Monitor cache hit rate: Low hit rates mean inefficient caching configuration

  8. Edge computing: Use CloudFront Functions for simple logic, Lambda@Edge for complex

CloudFront brings content closer to users while providing security and compute capabilities at the edge. Proper configuration maximises performance and minimises origin load.

Share this article