CDN Architecture: Serving Content at the Edge

Design CDN strategies that reduce latency, offload origin servers, and improve reliability. Covers cache hierarchies, cache invalidation, multi-CDN failover, edge computing, and the configuration patterns that prevent your CDN from serving stale content or bypassing your security controls.

Content delivery networks (CDNs) serve static and dynamic content from geographically distributed edge servers to reduce latency, improve availability, and offload origin infrastructure. This page details how to design, configure, and troubleshoot CDNs for edge delivery, focusing on the mechanics of content routing, caching, and update propagation.

Edge Content Routing and Request Flow

Requests to a CDN begin at a user’s client, which resolves the domain to a CDN edge IP via DNS. The edge server, often running a reverse proxy such as NGINX or Traefik, receives the HTTP request and applies routing rules before forwarding to an origin or another edge tier.

A typical edge request includes:

The edge server evaluates routing rules based on:

Routing decisions are made via a configuration language or set of rules expressed in a common format. For example, NGINX uses location blocks with if conditions:

server {
    listen 80;
    server_name static.example.com;

    location ~ ^/assets/(js|css)/.*\.(js|css)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        proxy_pass http://origin.example.com;
        proxy_cache edge_cache;
        proxy_cache_valid 200 1y;
        proxy_cache_key "$scheme$request_method$host$request_uri";
    }

    location /api/ {
        proxy_pass http://api.origin.com;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache api_cache;
        proxy_cache_use_stale error timeout updating http_500;
    }

    location / {
        try_files $uri $uri/ =404;
        add_header Cache-Control "public, max-age=3600";
    }
}

When a request hits an edge, the CDN evaluates a chain of matchers. The first matching rule determines the content source and caching behavior. If no rule matches, the request falls through to a default route.

Edge servers can use HTTP/2 or HTTP/3 to serve content with multiplexing and server push. A request for /index.html may trigger a push of /assets/css/main.css and /assets/js/app.js via Link headers:

HTTP/2 200 OK
Content-Type: text/html
Link: <https://static.example.com/assets/css/main.css>; rel=preload; as=style
Link: <https://static.example.com/assets/js/app.js>; rel=preload; as=script
Cache-Control: public, max-age=3600

Caching Strategy and Cache Invalidation

CDNs use hierarchical caching, with edge, regional, and origin tiers. The edge tier holds frequently accessed content close to users. Each cache entry has a Cache-Control header, which may be set by the origin or enforced by the CDN.

A typical cache entry includes:

The CDN evaluates freshness:

For cache invalidation, the CDN supports two primary methods:

  1. Time-based invalidation (TTL): The origin sets Cache-Control with max-age and s-maxage. The CDN refreshes content at the specified interval.
  1. Explicit invalidation (purge): A client or automation script sends a PURGE request to the CDN edge. The PURGE method is defined in HTTP/1.1 and widely supported:
PURGE /assets/js/main.js HTTP/1.1
Host: static.example.com
Authorization: Bearer cdn-secret-abc123

The CDN responds with:

The CDN may also support BULK PURGE via a POST request with a list of paths:

POST /purge HTTP/1.1
Host: static.example.com
Content-Type: application/json
Authorization: Bearer cdn-secret-abc123

{
  "paths": [
    "/assets/css/main.css",
    "/assets/js/app.js",
    "/images/logo.png"
  ],
  "strategy": "all"
}

The CDN supports three purge strategies:

A common failure mode is stale content after deployment: a new version of main.js is deployed but the CDN does not purge it. The edge returns the old file until max-age expires or a manual purge is triggered.

Origin Pull and Push Models

CDNs serve content from two models: origin pull and origin push.

In origin pull, the CDN fetches content on demand from a source server. The origin is typically a web server (Apache, Nginx) or a storage backend (S3, Cloud Storage). When an edge server does not have a requested file, it issues a GET to the origin using a Origin header:

GET /assets/js/main.js HTTP/1.1
Host: static.example.com
Origin: http://origin.example.com
Accept: application/javascript

The origin responds with:

The CDN caches the response and returns it to the client.

In origin push, content is proactively uploaded to the CDN. This is common for large assets like videos, fonts, or static site builds.

A typical push workflow uses curl or aws s3 cp:

aws s3 cp ./dist/ s3://cdn.example.com/ \
  --recursive \
  --acl public-read \
  --cache-control "public, max-age=31536000" \
  --content-type "application/javascript"

curl -X POST \
  -H "Authorization: Bearer cdn-secret-abc123" \
  -H "Content-Type: application/json" \
  -d '{"paths": ["/assets/js/main.js", "/assets/css/main.css"]}' \
  https://static.example.com/push

The CDN can validate the push via ETag or Last-Modified headers. If the origin sends ETag: "v2.3.1", the CDN checks for an existing ETag and only pushes if the content has changed.

A sharp edge: if the origin uses ETag but the CDN does not validate it, stale content can persist for months. The CDN may also support If-None-Match and If-Modified-Since in its origin pull logic.

Edge Compute and Edge Logic

Modern CDNs support edge compute, allowing logic to run at the edge. This includes:

Edge compute is typically implemented via a JavaScript runtime (like V8) that executes functions in response to incoming requests. A function is registered with the CDN via a configuration block:

{
  "function": {
    "name": "user-profile",
    "handler": "index.handler",
    "runtime": "nodejs18.x",
    "path": "/profile/*",
    "events": [
      {
        "type": "http",
        "methods": ["GET"],
        "headers": {
          "X-Request-ID": "{{request_id}}"
        }
      }
    ],
    "memory": 256,
    "timeout": 5000,
    "environment": {
      "DATABASE_URL": "postgresql://db.example.com/users"
    }
  }
}

The function receives a request object and returns a response:

exports.handler = async (event) => {
  const { path, headers, query } = event;
  const userId = path.split('/')[2];
  const db = await connect(process.env.DATABASE_URL);

  const user = await db.query(
    'SELECT name, avatar FROM users WHERE id = $1',
    [userId]
  );

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 'public, max-age=300'
    },
    body: JSON.stringify(user)
  };
};

The edge server evaluates the function and returns the result. The function can also modify the request or response, including setting X-Cache headers:

HTTP/1.1 200 OK
Content-Type: application/json
X-Cache: HIT, from edge
X-Edge-Function: user-profile
Cache-Control: public, max-age=300

Edge compute introduces subtle failure modes:

Failure Modes and Observability

CDN failures often go unnoticed until users report slow or broken content.

Common failure modes:

Observability is achieved through:

A typical access log entry:

192.0.2.1 - - [2025-04-05T12:34:56Z] "GET /assets/js/main.js HTTP/1.1" 200 2314 "edge-cache" "HIT" "user-agent=Chrome/125" "response-time=42ms"

The CDN may expose a /status endpoint that returns metrics:

GET /status HTTP/1.1
Host: static.example.com

{
  "cache": {
    "hit_rate": 0.87,
    "total_hits": 1248392,
    "total_misses": 183721,
    "average_age": 124.3,
    "top_10_paths": [
      "/assets/js/main.js",
      "/index.html",
      "/assets/css/main.css",
      "/api/users",
      "/images/logo.png"
    ]
  },
  "functions": {
    "user-profile": {
      "invocations": 1423,
      "errors": 12,
      "avg_duration_ms": 67.4
    },
    "api-proxy": {
      "invocations": 891,
      "errors": 3,
      "avg_duration_ms": 112.1
    }
  },
  "origin": {
    "status": "healthy",
    "last_check": "2025-04-05T12:35:00Z",
    "latency_ms": 42
  }
}

The CDN may also support X-Cache-Status header to indicate cache state:

X-Cache-Status: HIT, from edge, age=34, ttl=300
X-Cache-Status: MISS, from origin, age=0, ttl=3600
X-Cache-Status: STALE, from edge, updated, age=310, ttl=3600

These headers are critical for diagnosing performance issues and validating CDN behavior.

Configuration and Deployment

CDN configurations are typically versioned and deployed via CI/CD pipelines. A typical configuration file uses standard JSON or YAML:

cdn:
  domain: static.example.com
  edge:
    cache:
      default_ttl: 3600
      max_age: 31536000
      stale_while_revalidate: 86400
    routing:
      - path: "/assets/js/*.js"
        cache_control: "public, max-age=31536000, immutable"
        proxy_pass: "http://origin.example.com"
      - path: "/api/*"
        proxy_pass: "http://api.origin.com"
        cache_control: "public, max-age=600"
      - path: "/"
        try_files: ["$uri", "$uri/"]
        cache_control: "public, max-age=3600"
    functions:
      - name: user-profile
        path: "/profile/*"
        handler: "index.handler"
        runtime: "nodejs18.x"
        environment:
          DATABASE_URL: "postgresql://db.example.com/users"
        memory: 256
        timeout: 5000
  origin:
    pull:
      default_origin: "origin.example.com"
      health_check_path: "/health"
      health_check_interval: 30
      health_check_timeout: 5
    push:
      storage: "s3://cdn.example.com"
      cache_control: "public, max-age=31536000"

Deployment is done via git push to a CI pipeline that:

  1. Builds the application
  2. Uploads static assets to the CDN
  3. Updates the routing and function configuration
  4. Triggers a cache purge for key paths
  5. Validates the new configuration via health checks

A common mistake is deploying a new configuration without purging the cache, leading to a mismatch between the new routing rules and the cached content.

The CDN can be tested using curl and diff:

curl -H "Host: static.example.com" http://cdn.example.com/ \
  -H "Accept: application/json" \
  -H "User-Agent: Mobile/1.0" \
  --write-out "\n" \
  --silent \
  --output -

The output includes headers, X-Cache, X-Edge-Function, and Cache-Control, all of which can be verified against expected values.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.