Skip to main content
Technology & EngineeringNetworking Infrastructure214 lines

CDN Setup

CDN setup and optimization with Cloudflare, Fastly, and CloudFront for global content delivery

Quick Summary27 lines
You are an expert in CDN setup and optimization for building reliable networked systems.

## Key Points

- **s-maxage:** Overrides max-age for shared caches (CDN). Lets you set different TTLs for CDN vs browser.
- **stale-while-revalidate:** Serve stale content while fetching fresh copy in background.
- **immutable:** Tells the browser not to revalidate even on reload (for versioned assets).
- **Use hashed filenames** for static assets (`main.a1b2c3.js`) with immutable cache headers — this eliminates the need for cache invalidation on deploys.
- **Enable compression** (Brotli + gzip) at the CDN edge to reduce bandwidth and improve load times without burdening the origin.
- **Set up origin shields** to collapse multiple edge requests into a single origin fetch, protecting your origin during cache misses and traffic spikes.

## Quick Example

```
User (Tokyo) ──→ Edge PoP (Tokyo) ──→ Origin Shield ──→ Origin Server (US-East)
                   ↑ cache hit                            ↑ cache miss
                   └─ serve immediately                   └─ fetch, cache, serve
```

```
Cache-Control: public, max-age=31536000, immutable    # Static assets (hashed filenames)
Cache-Control: public, max-age=0, must-revalidate     # HTML pages
Cache-Control: private, no-store                       # Authenticated/dynamic content
Cache-Control: public, s-maxage=3600, max-age=60      # CDN caches 1h, browser caches 1m
```
skilldb get networking-infrastructure-skills/CDN SetupFull skill: 214 lines
Paste into your CLAUDE.md or agent config

CDN Setup — Networking & Infrastructure

You are an expert in CDN setup and optimization for building reliable networked systems.

Core Philosophy

Overview

A Content Delivery Network (CDN) caches and serves content from edge servers geographically close to users, reducing latency, offloading origin traffic, and improving availability. This skill covers CDN architecture, configuration for major providers (Cloudflare, Fastly, CloudFront), cache control strategies, and performance optimization.

Core Concepts

CDN Architecture

User (Tokyo) ──→ Edge PoP (Tokyo) ──→ Origin Shield ──→ Origin Server (US-East)
                   ↑ cache hit                            ↑ cache miss
                   └─ serve immediately                   └─ fetch, cache, serve

Cache Control Headers

Cache-Control: public, max-age=31536000, immutable    # Static assets (hashed filenames)
Cache-Control: public, max-age=0, must-revalidate     # HTML pages
Cache-Control: private, no-store                       # Authenticated/dynamic content
Cache-Control: public, s-maxage=3600, max-age=60      # CDN caches 1h, browser caches 1m

Key directives:

  • s-maxage: Overrides max-age for shared caches (CDN). Lets you set different TTLs for CDN vs browser.
  • stale-while-revalidate: Serve stale content while fetching fresh copy in background.
  • immutable: Tells the browser not to revalidate even on reload (for versioned assets).

Cache Key

The cache key determines what constitutes a unique cached object. Typically: scheme + host + path + query string. Misconfigured cache keys cause either cache pollution (too many variants) or dangerous content mixing (too few variants).

Implementation Patterns

Cloudflare Configuration

# wrangler.toml for Cloudflare Workers (edge logic)
name = "my-cdn-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[env.production]
routes = [
  { pattern = "cdn.example.com/*", zone_name = "example.com" }
]
// Cloudflare Worker: custom caching logic
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Static assets — long cache
    if (url.pathname.startsWith('/assets/')) {
      const response = await fetch(request);
      const headers = new Headers(response.headers);
      headers.set('Cache-Control', 'public, max-age=31536000, immutable');
      return new Response(response.body, { ...response, headers });
    }

    // API responses — short CDN cache
    if (url.pathname.startsWith('/api/')) {
      const cacheKey = new Request(url.toString(), request);
      const cache = caches.default;
      let response = await cache.match(cacheKey);
      if (!response) {
        response = await fetch(request);
        const headers = new Headers(response.headers);
        headers.set('Cache-Control', 's-maxage=60');
        response = new Response(response.body, { ...response, headers });
        await cache.put(cacheKey, response.clone());
      }
      return response;
    }

    return fetch(request);
  },
};

CloudFront with Terraform

resource "aws_cloudfront_distribution" "cdn" {
  origin {
    domain_name = aws_s3_bucket.assets.bucket_regional_domain_name
    origin_id   = "S3Assets"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
    }
  }

  enabled             = true
  default_root_object = "index.html"
  aliases             = ["cdn.example.com"]
  price_class         = "PriceClass_100"  # US, Canada, Europe only

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD", "OPTIONS"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "S3Assets"
    viewer_protocol_policy = "redirect-to-https"
    compress               = true

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    min_ttl     = 0
    default_ttl = 86400
    max_ttl     = 31536000
  }

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate.cert.arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}

Fastly VCL Snippet

sub vcl_recv {
  # Normalize Accept-Encoding to reduce cache variants
  if (req.http.Accept-Encoding) {
    if (req.http.Accept-Encoding ~ "br") {
      set req.http.Accept-Encoding = "br";
    } elsif (req.http.Accept-Encoding ~ "gzip") {
      set req.http.Accept-Encoding = "gzip";
    } else {
      unset req.http.Accept-Encoding;
    }
  }

  # Strip tracking query params from cache key
  set req.url = querystring.regfilter(req.url, "^(utm_|fbclid|gclid)");
}

sub vcl_fetch {
  # Enable stale-while-revalidate
  if (beresp.status == 200) {
    set beresp.stale_while_revalidate = 60s;
    set beresp.stale_if_error = 300s;
  }
}

Cache Invalidation

# Cloudflare — purge by URL
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://cdn.example.com/assets/main.css"]}'

# CloudFront — create invalidation
aws cloudfront create-invalidation \
  --distribution-id E1234567890 \
  --paths "/index.html" "/api/*"

# Fastly — instant purge by surrogate key
curl -X POST "https://api.fastly.com/service/${SERVICE_ID}/purge/product-123" \
  -H "Fastly-Key: ${FASTLY_TOKEN}"

Best Practices

  • Use hashed filenames for static assets (main.a1b2c3.js) with immutable cache headers — this eliminates the need for cache invalidation on deploys.
  • Enable compression (Brotli + gzip) at the CDN edge to reduce bandwidth and improve load times without burdening the origin.
  • Set up origin shields to collapse multiple edge requests into a single origin fetch, protecting your origin during cache misses and traffic spikes.

Common Pitfalls

  • Caching authenticated or personalized content: If your cache key does not account for cookies or auth headers, user A may see user B's data. Always set Cache-Control: private, no-store for authenticated responses.
  • Query string cache pollution: Without normalizing or stripping irrelevant query parameters (analytics tags, tracking IDs), the same content gets cached hundreds of times under different keys, destroying your hit ratio.

Anti-Patterns

Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.

Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.

Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add networking-infrastructure-skills

Get CLI access →