system-design56 min read

How to Design a Content Delivery Network (CDN) — A Senior+ Guide | Ayodhyya

How to Design a Content Delivery Network (CDN)

Building a Cloudflare/Akamai-Scale System — Edge Caching, DNS Routing, DDoS Protection, Edge Computing

Senior+ System Design Guide 10,000+ Words 20 Deep-Dive Sections C# · Mermaid · Real-World Case Studies

1. Introduction & Why CDNs Matter

A Content Delivery Network (CDN) is a geographically distributed network of proxy servers and data centers that deliver web content to users based on their geographic proximity. CDNs have become the backbone of the modern internet — by some estimates, over 75% of all internet traffic traverses a CDN at some point. Companies like Cloudflare, Akamai, Fastly, and Amazon CloudFront operate networks spanning hundreds of data centers across every inhabited continent, collectively handling hundreds of petabytes of traffic per day.

The fundamental problem a CDN solves is simple: the speed of light is finite, and network hops introduce latency. A user in Tokyo requesting content hosted on a server in Virginia will experience significantly higher latency than if the same content were served from a data center in Tokyo. Moreover, the origin server has finite bandwidth and compute capacity. Without a CDN, every user request hits the origin, creating a bottleneck that limits scalability and increases the risk of cascading failures.

CDNs address this through three core mechanisms: caching (storing copies of content at edge locations close to users), routing optimization (using DNS and anycast to direct users to the nearest or optimal edge), and security hardening (absorbing DDoS attacks and filtering malicious traffic before it reaches the origin). Modern CDNs have evolved far beyond simple caching — they now offer edge computing platforms, real-time analytics, image optimization, bot management, zero-trust networking, and more.

The Scale of the Problem

Consider the numbers involved in operating a major CDN:

  • Cloudflare operates in 310+ cities across 120+ countries, processing over 60 million HTTP requests per second at peak.
  • Akamai's network carries 15-30% of all web traffic, with over 365,000 servers in 135 countries.
  • A single flash event — like a major product launch or a viral social media post — can generate millions of requests per second for a single piece of content within minutes.
  • DDoS attacks have grown to over 3.47 Tbps (as recorded in 2024), requiring CDNs to absorb traffic volumes that would overwhelm any single data center.

Designing a CDN is not merely a caching problem. It is a distributed systems challenge that spans networking (BGP, anycast, DNS), storage (hierarchical caching with strict consistency requirements), security (TLS termination, WAF, DDoS mitigation), compute (edge functions, real-time transformations), and operations (global fleet management, incident response across time zones). This guide walks through each of these dimensions in depth.

Why this matters for interviews: CDN design questions appear frequently at Staff+ levels because they test breadth (networking, storage, security, operations) and depth (cache coherence, TLS, DDoS) simultaneously. You are expected to reason about trade-offs across all these layers.

Historical Context

The first CDN, Akamai, was founded in 1998 at MIT. The original insight was that by distributing copies of popular content across a network of servers, you could dramatically reduce latency and improve reliability. Early CDNs focused exclusively on static content — images, stylesheets, and downloadable files. Over time, CDNs evolved to handle dynamic content (through techniques like edge-side includes and partial caching), video streaming (through adaptive bitrate protocols like HLS and DASH), and eventually general-purpose compute (through edge computing platforms like Cloudflare Workers and Fastly Compute).

The modern CDN has become an application delivery platform. It is the first line of defense against attacks, the closest compute surface to end users, and the critical infrastructure that makes the global web feel fast and responsive. Understanding how to design one from first principles is an essential skill for senior engineers working on distributed systems at scale.

2. Functional & Non-Functional Requirements

Functional Requirements

  1. Content Caching & Delivery: Cache and serve static assets (HTML, CSS, JS, images, video segments, fonts, API responses) from edge locations closest to end users.
  2. Origin Offload: Reduce origin server load by serving cached content from edge, with configurable TTLs and cache policies per content type.
  3. Cache Invalidation: Support both time-based expiration (TTL) and on-demand purging (instant purge across all edge nodes within seconds).
  4. TLS Termination: Terminate TLS at edge servers, support TLS 1.2/1.3, SNI-based certificate selection, and automatic certificate provisioning via ACME.
  5. DDoS Protection: Absorb volumetric, protocol, and application-layer DDoS attacks at the network edge without impacting legitimate traffic.
  6. WAF (Web Application Firewall): Inspect HTTP traffic for OWASP Top 10 vulnerabilities (SQLi, XSS, etc.) and apply custom rulesets.
  7. DNS Resolution: Provide authoritative DNS with low-latency resolution, supporting CNAME, A/AAAA records, and CNAME flattening.
  8. Edge Computing: Execute customer-defined logic (serverless functions) at edge locations for request/response transformation.
  9. Load Balancing: Distribute origin traffic across multiple origin servers with health checks, failover, and weighted routing.
  10. Real-Time Analytics: Provide customers with real-time dashboards for traffic, cache hit rates, latency, errors, and security events.
  11. Image Optimization: On-the-fly image resizing, format conversion (WebP, AVIF), and compression.
  12. Rate Limiting & Bot Management: Identify and throttle abusive traffic patterns based on configurable rules.

Non-Functional Requirements

Requirement Target Rationale
Availability 99.99%+ (five nines) CDN downtime affects millions of websites; contractual SLAs demand near-perfect uptime
Latency (p99) < 50ms for cached content Edge servers must be within one network hop of most users
Cache Hit Ratio > 95% for static assets High hit ratio is the core value proposition of a CDN
Throughput 100+ Tbps aggregate network capacity Must handle flash crowds and absorb large DDoS attacks
Purge Latency < 5 seconds globally Customers need fast invalidation for security patches and content updates
TLS Handshake < 5ms (with TLS 1.3 0-RTT) TLS must not add perceptible latency to requests
Edge Locations 300+ PoPs globally Must be within 50ms of 95% of the internet-connected population
Security PCI DSS, SOC 2 Type II, ISO 27001 Enterprise customers require compliance certifications
Scalability Linear horizontal scaling Adding capacity should be proportional to traffic growth
Observability Sub-second metric granularity Customers and operations teams need real-time visibility
Interview Tip: When asked about CDN design, always clarify whether you are designing a CDN for internal use (like Netflix's Open Connect) or a general-purpose CDN sold as a service (like Cloudflare). The requirements differ significantly — an internal CDN optimizes for a specific content type, while a commercial CDN must handle arbitrary workloads from thousands of customers simultaneously.

Scope Boundaries

For this design, we focus on the HTTP/HTTPS content delivery use case. We do not design for DNS-only services (though we discuss DNS as it relates to CDN routing), raw TCP/UDP proxying, or dedicated DDoS-only scrubbing services. Our CDN is the full-stack product: edge caching, security, edge compute, and observability.

3. Capacity Estimation & Back-of-Envelope Math

Traffic Assumptions

Let us assume our CDN serves approximately 10 million websites with a combined peak traffic of 50 million requests per second. The average response size for cached content is 50 KB (considering a mix of HTML, CSS, JS, images, and small API responses).

Bandwidth Calculation:
50M req/s × 50 KB = 2.5 GB/s = 20 Gbps of sustained throughput per edge PoP.
With 300 PoPs: 300 × 20 Gbps = 6 Tbps aggregate egress.
Peak-to-average ratio of 3x: 6 Tbps × 3 = 18 Tbps peak capacity needed.

Storage Estimation

Assuming an average cacheable object size of 50 KB and we want to cache 100 million unique objects across the network (a reasonable estimate for popular web content):

Component Calculation Result
Object count (global) 100M unique objects 100,000,000
Average object size 50 KB 50 KB
Total cache (global) 100M × 50 KB 5 TB
Per PoP cache (300 PoPs) 5 TB / 300 ~17 GB per PoP (hot content)
Per server SSD Typical edge server 2-4 TB NVMe
Servers per PoP Varies by location 20-200 servers

Cache Hit Ratio Impact

If our cache hit ratio is 95%, only 5% of requests reach the origin:

  • Requests to origin: 50M × 0.05 = 2.5M requests/second
  • Origin bandwidth: 2.5M × 50 KB = 125 GB/s = 1 Tbps
  • If hit ratio drops to 90%, origin load doubles to 2 Tbps — potentially overwhelming origin infrastructure.

Connection Estimation

At 50M requests/second with an average request duration of 200ms:

  • Concurrent connections per PoP: 50M / 300 PoPs × 0.2s = ~33,000 concurrent connections per PoP
  • Per server (100 servers/PoP): 33,000 / 100 = 330 concurrent connections per server
  • Each server handles approximately 500 requests/second — well within the capacity of a modern event-driven server using epoll/io_uring.

DDoS Absorption Capacity

A major DDoS attack might generate 3+ Tbps of traffic. Our CDN must absorb this without impacting legitimate traffic:

  • Each PoP must absorb ~10 Gbps of attack traffic (distributed across 300 PoPs)
  • With 300 PoPs each having 20 Gbps of capacity, aggregate absorption capacity is 6 Tbps
  • Over-provisioning factor of 3x gives us 18 Tbps — sufficient for current generation attacks
Key Insight: The most critical capacity dimension is not bandwidth but connection handling. Modern DDoS attacks use millions of slow loris connections, amplification attacks, and slow-rate application-layer attacks that exhaust server resources without generating high bandwidth. Your edge servers must handle millions of concurrent connections efficiently, which is why event-driven architectures (not thread-per-connection) are essential.

4. Data Model & Metadata Schema

A CDN requires several data stores to manage configuration, caching metadata, analytics, and security policies. The data model is not a traditional relational schema — it is a distributed metadata layer that must be globally consistent and extremely fast to query at the edge.

Core Entities

C#
// Represents a customer's zone (domain) configuration
public class Zone
{
    public string ZoneId { get; set; }              // Unique identifier
    public string DomainName { get; set; }          // e.g., "example.com"
    public string CustomerId { get; set; }          // Owning customer
    public ZoneStatus Status { get; set; }          // Active, Suspended, Purging
    public List<OriginServer> Origins { get; set; } // Origin server definitions
    public CachePolicy CachePolicy { get; set; }    // Default caching rules
    public List<CacheRule> CacheRules { get; set; } // Path-specific rules
    public SecurityPolicy SecurityPolicy { get; set; } // WAF & DDoS config
    public TLSConfig TLSConfig { get; set; }        // Certificate & TLS settings
    public RateLimitConfig RateLimits { get; set; }  // Rate limiting rules
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
    public Dictionary<string, string> CustomMetadata { get; set; }
}

public class OriginServer
{
    public string OriginId { get; set; }
    public string Address { get; set; }             // IP or hostname
    public int Port { get; set; }                   // Default 443
    public OriginProtocol Protocol { get; set; }    // HTTP, HTTPS, HTTP2
    public int Weight { get; set; }                 // For weighted load balancing
    public HealthCheckConfig HealthCheck { get; set; }
    public OriginShield OriginShield { get; set; }  // Shield assignment
}

public class CacheRule
{
    public string RuleId { get; set; }
    public string Pattern { get; set; }             // Glob pattern: "/assets/*"
    public CacheAction Action { get; set; }         // Cache, Bypass, ForceCache
    public int TTL { get; set; }                    // Seconds
    public int EdgeTTL { get; set; }                // Edge cache TTL
    public int BrowserTTL { get; set; }             // Browser cache TTL
    public bool StaleWhileRevalidate { get; set; }
    public bool StaleIfError { get; set; }
    public CacheKeyLayout CacheKey { get; set; }    // How to construct cache key
}

public enum CacheAction
{
    Cache,          // Normal caching with TTL
    Bypass,         // Never cache
    ForceCache,     // Cache even if origin sends no-cache
    Managed,        // CDN-managed caching heuristics
    IgnoreQueryOrder // Normalize query string
}

// Cache metadata stored per edge server
public class CacheEntry
{
    public string CacheKey { get; set; }            // Hash of URL + headers
    public string ContentHash { get; set; }         // SHA-256 of content
    public long ContentLength { get; set; }
    public string ContentType { get; set; }
    public string ContentEncoding { get; set; }     // gzip, br, identity
    public DateTime CachedAt { get; set; }
    public DateTime ExpiresAt { get; set; }
    public CacheValidationTags ValidationTags { get; set; }
    public List<string> VaryHeaders { get; set; }
    public long HitCount { get; set; }
    public long ByteSize { get; set; }
    public CacheOrigin ShieldOrigin { get; set; }
    public PurgeStatus PurgeStatus { get; set; }
}

public class PurgeRecord
{
    public string PurgeId { get; set; }
    public string ZoneId { get; set; }
    public PurgeType Type { get; set; }             // URL, Tag, Hostname, Prefix
    public List<string> Targets { get; set; }      // URLs or tags to purge
    public PurgeStatus Status { get; set; }         // Pending, InProgress, Complete
    public DateTime IssuedAt { get; set; }
    public DateTime CompletedAt { get; set; }
    public int AffectedEdges { get; set; }
}

public enum PurgeType { URL, Tag, Hostname, Wildcard, Prefix }
public enum PurgeStatus { Pending, InProgress, Complete, Failed }

Analytics Data Model

C#
// Real-time request log entry (sampled at edge, aggregated centrally)
public class RequestLogEntry
{
    public string RequestId { get; set; }           // Unique ID
    public string ZoneId { get; set; }
    public string ClientIP { get; set; }
    public string ClientCountry { get; set; }
    public string ClientASN { get; set; }
    public string EdgeLocation { get; set; }        // Which PoP handled it
    public string OriginIP { get; set; }            // Which origin was hit
    public string Method { get; set; }              // GET, POST, etc.
    public string URI { get; set; }
    public int ResponseCode { get; set; }
    public long ResponseBytes { get; set; }
    public long CacheBytes { get; set; }            // Bytes served from cache
    public bool CacheHit { get; set; }
    public bool CacheStatus { get; set; }           // HIT, MISS, EXPIRED, REVALIDATED
    public double ResponseTimeMs { get; set; }
    public double OriginTimeMs { get; set; }
    public string RayId { get; set; }               // Distributed trace ID
    public WAFAction WAFAction { get; set; }
    public string ThreatScore { get; set; }         // Bot/threat classification
    public DateTime Timestamp { get; set; }
}

// Aggregated metrics rollup (1-minute windows)
public class MetricsRollup
{
    public string ZoneId { get; set; }
    public string EdgeLocation { get; set; }
    public DateTime WindowStart { get; set; }
    public long TotalRequests { get; set; }
    public long CacheHits { get; set; }
    public long CacheMisses { get; set; }
    public long TotalBytesServed { get; set; }
    public long TotalBytesFromOrigin { get; set; }
    public double AvgResponseTimeMs { get; set; }
    public double P95ResponseTimeMs { get; set; }
    public double P99ResponseTimeMs { get; set; }
    public long Error4xxCount { get; set; }
    public long Error5xxCount { get; set; }
    public long BlockedRequests { get; set; }
    public Dictionary<int, long> StatusCodeDistribution { get; set; }
    public Dictionary<string, long> TopURIs { get; set; }
}

Storage Layer

Data Type Storage System Reasoning
Zone configuration Distributed KV store (etcd/ZooKeeper) Strong consistency needed for config propagation to all edges
Cache metadata Local SSD + distributed invalidation bus Must be extremely fast (local reads); invalidation via pub/sub
Purge commands Global queue (Kafka) + distributed broadcast Ordered, durable queue; broadcast to all edges via pub/sub
Request logs Stream processing (Kafka -> ClickHouse/TimescaleDB) High write throughput; analytical queries on time-series data
Security rules Proprietary rule engine + distributed KV Rules must be evaluated in microseconds at edge; version-controlled
Certificates Encrypted KV store + ACME automation TLS certificates must be stored securely; auto-renewal via ACME

5. High-Level Architecture Overview

The CDN architecture follows a hierarchical model with three primary tiers: the edge layer (closest to end users), the mid-tier / shield layer (regional aggregation), and the origin layer (where customer servers reside). Control plane services run centrally and propagate configuration to all edge nodes via a high-speed distribution network.

graph TB subgraph "End Users" U1[User - New York] U2[User - London] U3[User - Tokyo] U4[User - Sydney] end subgraph "DNS Layer" DNS[Global DNS - Anycast] GSLB[GeoDNS / Load Balancer] end subgraph "Edge Layer (300+ PoPs)" E1[Edge PoP - NYC] E2[Edge PoP - LHR] E3[Edge PoP - NRT] E4[Edge PoP - SYD] end subgraph "Mid-Tier / Shield Layer" M1[Shield - US-East] M2[Shield - EU-West] M3[Shield - APAC] end subgraph "Control Plane" CP[Config Distribution] PURGE[Purge Orchestrator] ANALYTICS[Analytics Pipeline] CERTS[Certificate Manager] end subgraph "Origin Servers" O1[Origin - US] O2[Origin - EU] end U1 --> DNS U2 --> DNS U3 --> DNS U4 --> DNS DNS --> GSLB GSLB --> E1 GSLB --> E2 GSLB --> E3 GSLB --> E4 E1 -->|Cache Miss| M1 E2 -->|Cache Miss| M2 E3 -->|Cache Miss| M3 E4 -->|Cache Miss| M3 M1 -->|Cache Miss| O1 M2 -->|Cache Miss| O2 CP --> E1 CP --> E2 CP --> E3 CP --> E4 PURGE --> E1 PURGE --> E2 PURGE --> E3 PURGE --> E4 E1 --> ANALYTICS E2 --> ANALYTICS E3 --> ANALYTICS E4 --> ANALYTICS

Request Flow

When a user requests https://example.com/image.jpg:

  1. DNS Resolution: The user's recursive resolver queries our authoritative DNS. Anycast routing directs the query to the nearest edge PoP. GeoDNS returns the IP of the optimal edge server.
  2. Edge Processing: The request arrives at the edge PoP. The edge server performs TLS termination, evaluates WAF rules, checks rate limits, and looks up the cache.
  3. Cache HIT: If the content is cached and valid, it is served directly from the edge server's local SSD. The response includes appropriate Age and X-Cache headers.
  4. Cache MISS: If not cached, the edge server forwards the request to the mid-tier shield for that region. The shield checks its larger cache.
  5. Shield MISS: If the shield also misses, it forwards to the origin server, caching the response on the way back through both shield and edge layers.
  6. Response Path: The response flows back: Origin → Shield (cache) → Edge (cache) → User. Each layer independently caches the response with appropriate TTLs.

Control Plane vs. Data Plane

The CDN has a clean separation between the data plane (request handling at edge) and the control plane (configuration, monitoring, certificate management):

Data Plane: Handles actual HTTP requests. Runs on every edge server. Must be fast, resilient, and function even if the control plane is temporarily unreachable. Written in Rust/C/C++ for maximum performance. Operates on locally cached configuration and cached content.
Control Plane: Manages zone configuration, certificate provisioning, purge orchestration, analytics aggregation, and customer dashboards. Runs in central and regional control clusters. Can tolerate higher latency and occasional unavailability without affecting end-user traffic.

Geographic Distribution

A modern CDN deploys edge servers in three tiers of location:

  • Major metros (Tier 1): 50+ locations — New York, London, Tokyo, Singapore, Sydney, São Paulo, Mumbai, Frankfurt, etc. These PoPs have 50-200 servers each and handle the highest traffic volumes.
  • Regional hubs (Tier 2): 100+ locations — Secondary cities like Dallas, Amsterdam, Seoul, Johannesburg, etc. These PoPs have 10-50 servers each.
  • Emerging markets (Tier 3): 150+ locations — Smaller markets to reduce latency for underserved regions. These PoPs have 5-20 servers each and may be deployed on bare metal from local ISPs.

The network interconnecting these PoPs is a combination of dedicated fiber (for high-traffic paths), public internet (for lower-traffic paths), and partner networks. The key metric is that any edge PoP should be able to reach any origin or shield within 50ms of additional latency.

6. Edge Server Architecture

The edge server is the workhorse of the CDN. Every HTTP request passes through it, and it must handle TLS termination, caching, security inspection, and response delivery — all within microseconds. The architecture of the edge server is designed for maximum throughput and minimal latency.

Software Stack

graph TB subgraph "Edge Server Process" NIC[10/25/100 GbE NIC] KERNEL[Linux Kernel - io_uring] LB[Load Balancer - Accept Queue] TLS[TLS Termination - boringssl] WAF[WAF Engine] RATE[Rate Limiter] CACHE[Cache Engine - SSD/DRAM] COMPRESS[Compression - Brotli/gzip] HTTP[HTTP Response Builder] LOG[Async Log Writer] end NIC --> KERNEL KERNEL --> LB LB --> TLS TLS --> WAF WAF --> RATE RATE --> CACHE CACHE -->|Hit| COMPRESS CACHE -->|Miss| ORIGIN[Origin Connector] ORIGIN --> COMPRESS COMPRESS --> HTTP HTTP --> LOG LOG --> KAFKA[Kafka Producer]

Event-Driven Architecture

Edge servers use a single-threaded (or少量 threads) event-driven architecture built on io_uring or epoll. This eliminates thread contention, context switching overhead, and allows efficient handling of hundreds of thousands of concurrent connections with minimal memory overhead.

C#
// Simplified edge server request pipeline
public class EdgeRequestPipeline
{
    private readonly TlsTerminator _tls;
    private readonly WafEngine _waf;
    private readonly RateLimiter _rateLimiter;
    private readonly CacheEngine _cache;
    private readonly OriginConnector _origin;
    private readonly ResponseCompressor _compressor;
    private readonly PurgeBus _purgeBus;

    public async ValueTask<EdgeResponse> ProcessRequest(
        EdgeRequest request, CancellationToken ct)
    {
        var startTime = ValueStopwatch.StartNew();

        // Step 1: TLS Termination
        var plainRequest = await _tls.DecryptAsync(request, ct);

        // Step 2: WAF Inspection
        var wafResult = _waf.Evaluate(plainRequest);
        if (wafResult.Action == WafAction.Block)
        {
            return EdgeResponse BlockedResponse(wafResult);
        }

        // Step 3: Rate Limiting
        if (!_rateLimiter.AllowRequest(plainRequest.ClientIP, plainRequest.ZoneId))
        {
            return EdgeResponse.RateLimited();
        }

        // Step 4: Cache Lookup
        var cacheKey = BuildCacheKey(plainRequest);
        var cacheEntry = _cache.Get(cacheKey);

        if (cacheEntry != null && !cacheEntry.IsExpired)
        {
            // Cache HIT path
            return await ServeFromCache(plainRequest, cacheEntry, ct);
        }

        // Step 5: Origin Fetch (Cache MISS)
        var originResponse = await _origin.FetchAsync(plainRequest, ct);

        // Step 6: Cache Store
        if (IsCacheable(originResponse))
        {
            _cache.Store(cacheKey, originResponse);
        }

        // Step 7: Compress & Return
        return await _compressor.CompressAsync(originResponse, ct);
    }

    private string BuildCacheKey(EdgeRequest request)
    {
        var builder = new ValueStringBuilder(stackalloc byte[512]);
        builder.Append(request.Method);
        builder.Append(':');
        builder.Append(request.Uri);
        if (request.HasRelevantVaryHeaders)
        {
            builder.Append('|');
            foreach (var header in request.GetVaryHeaders())
            {
                builder.Append(header.Name);
                builder.Append('=');
                builder.Append(header.Value);
                builder.Append(';');
            }
        }
        return SHA256.HashToString(builder.AsSpan());
    }
}

Hardware Specifications

Component Tier 1 PoP Server Tier 3 PoP Server
CPU Dual AMD EPYC 9654 (192 cores) Single AMD EPYC 7763 (64 cores)
RAM 512 GB DDR5 128 GB DDR4
NVMe SSD 4 × 3.84 TB Samsung PM1733 2 × 1.92 TB Samsung PM1733
Network Dual 100 GbE (bonded) Dual 25 GbE (bonded)
Estimated req/s 500,000+ HTTP req/s 100,000 HTTP req/s
Estimated throughput 40+ Gbps 10+ Gbps
Critical Design Decision: The edge server cache uses a two-level hierarchy: hot content in DRAM (LRU with ~10,000 objects) and warm content on NVMe SSD (LRU with millions of objects). DRAM lookups take ~100ns while NVMe reads take ~10-50μs. The DRAM cache alone can serve 30-40% of requests for popular content. The SSD cache serves the long tail. This hybrid approach provides the performance of an in-memory cache with the capacity of disk-based storage.

Connection Pooling and Origin Reuse

The edge server maintains persistent HTTP/2 or HTTP/3 connection pools to origin servers and shield nodes. Connection establishment (TCP handshake + TLS handshake) is expensive — typically 1-3 round trips. By reusing connections, the edge server amortizes this cost across many requests. The connection pool is managed per-origin with configurable limits, health monitoring, and automatic failover to backup origins.

7. DNS Resolution & Anycast Routing

DNS is the critical first mile of CDN routing. When a user requests example.com, the CDN must direct them to the optimal edge server — typically the one topologically closest. This is achieved through a combination of Anycast BGP and GeoDNS.

Anycast

Anycast is a routing technique where multiple geographically distributed servers advertise the same IP address prefix via BGP. The Border Gateway Protocol (BGP) naturally routes packets to the topologically nearest node advertising that prefix. This means every edge PoP in a given region might advertise the same /24 (or /48 for IPv6) prefix, and routers automatically direct traffic to the nearest PoP.

graph LR subgraph "Anycast Cluster - 198.51.100.0/24" A1[BGP Announce: 198.51.100.0/24 - NYC] A2[BGP Announce: 198.51.100.0/24 - LHR] A3[BGP Announce: 198.51.100.0/24 - NRT] end subgraph "Global BGP Routing Table" R1[AS Path: NYC - 1 hop] R2[AS Path: LHR - 2 hops] R3[AS Path: NRT - 3 hops] end US[US User] -->|BGP selects shortest AS path| A1 EU[EU User] -->|BGP selects shortest AS path| A2 JP[JP User] -->|BGP selects shortest AS path| A3

Anycast has several advantages for CDNs:

  • Natural DDoS absorption: Attack traffic is distributed across all PoPs rather than overwhelming a single data center.
  • Zero-configuration routing: BGP handles failover automatically — if a PoP goes down, BGP withdraws the route and traffic shifts to the next-nearest PoP.
  • Low latency: Users are automatically routed to the closest PoP without any client-side logic.

GeoDNS

While anycast handles coarse-grained routing (directing users to the nearest PoP), GeoDNS provides fine-grained control. When the CDN's authoritative DNS receives a query, it can examine the client's IP address (specifically, the EDNS Client Subnet option) and return a specific IP address for the most appropriate edge server.

C#
// DNS request handler with GeoDNS logic
public class CdnDnsResolver
{
    private readonly IpGeolocationDatabase _geoDb;
    private readonly LoadMonitor _loadMonitor;
    private readonly EdgeNodeRegistry _edgeRegistry;

    public DnsResponse ResolveQuery(DnsQuery query)
    {
        var clientIp = query.EDNSSubnet ?? query.SourceIP;
        var clientGeo = _geoDb.Lookup(clientIp);

        // Find nearest edge PoPs
        var candidatePops = _edgeRegistry
            .GetPoPsForRegion(clientGeo.Continent, clientGeo.Country)
            .Where(pop => pop.Status == PoPStatus.Healthy)
            .OrderBy(pop => GeoDistance(clientGeo, pop.Location))
            .ThenBy(pop => pop.CurrentLoad)
            .Take(3)
            .ToList();

        if (candidatePops.Count == 0)
        {
            // Fallback: return any healthy PoP
            candidatePops = _edgeRegistry
                .GetHealthyPoPs()
                .OrderBy(_ => Random.Shared.Next())
                .Take(3)
                .ToList();
        }

        // Weighted selection based on current load
        var selectedPop = WeightedSelect(candidatePops);

        return new DnsResponse
        {
            Name = query.Name,
            Type = DnsRecordType.A,
            Address = selectedPop.AnycastIPv4,
            TTL = 30, // Short TTL for dynamic routing
            AdditionalRecords = candidatePops
                .Select(p => new DnsRecord(p.AnycastIPv4, 30))
                .ToList()
        };
    }

    private double GeoDistance(GeoLocation a, GeoLocation b)
    {
        // Haversine formula for great-circle distance
        const double R = 6371; // Earth's radius in km
        var dLat = ToRadians(b.Lat - a.Lat);
        var dLon = ToRadians(b.Lon - a.Lon);
        var sinLat = Math.Sin(dLat / 2);
        var sinLon = Math.Sin(dLon / 2);
        var h = sinLat * sinLat +
                Math.Cos(ToRadians(a.Lat)) *
                Math.Cos(ToRadians(b.Lat)) *
                sinLon * sinLon;
        return R * 2 * Math.Atan2(Math.Sqrt(h), Math.Sqrt(1 - h));
    }
}

CNAME Flattening

Many CDN customers already have DNS configurations that use CNAME records (e.g., www.example.com CNAME cdn.example.com). CNAME at the apex domain (the bare domain like example.com) is forbidden by DNS RFCs because it conflicts with other record types (SOA, NS). CDNs solve this with CNAME flattening — the CDN's DNS server resolves the CNAME chain internally and returns only A/AAAA records, effectively flattening the CNAME into direct IP addresses.

Trade-off: Anycast provides natural resilience but can be suboptimal for certain traffic patterns. For example, if two PoPs in the same city have very different loads, BGP will still route to the nearest PoP regardless of load. GeoDNS supplements anycast by incorporating real-time load data into DNS responses. However, GeoDNS responses are cached for the duration of the DNS TTL, so they cannot react to sub-second traffic spikes. The combination of anycast (for fast failover and DDoS resilience) and GeoDNS (for load-aware routing) is the industry standard approach.

8. Cache Hierarchy — Edge, Mid-Tier, Origin

CDN caching is not a flat architecture — it is a carefully designed hierarchy that balances hit ratio, latency, bandwidth cost, and origin load. The three tiers (edge, mid-tier/shield, origin) each serve a specific purpose in this hierarchy.

graph TB subgraph "Tier 1: Edge Cache (Local SSD + DRAM)" EC1[Edge: NYC - 2TB SSD] EC2[Edge: LHR - 2TB SSD] EC3[Edge: NRT - 2TB SSD] end subgraph "Tier 2: Mid-Tier / Shield Cache (Large SSD)" MC1[Shield: US-East - 100TB SSD] MC2[Shield: EU-West - 100TB SSD] MC3[Shield: APAC - 100TB SSD] end subgraph "Tier 3: Origin Storage" OS1[Origin Server 1] OS2[Origin Server 2] end U1[User NYC] --> EC1 U2[User London] --> EC2 U3[User Tokyo] --> EC3 EC1 -->|Miss| MC1 EC2 -->|Miss| MC2 EC3 -->|Miss| MC3 MC1 -->|Miss| OS1 MC1 -->|Miss| OS2 MC2 -->|Miss| OS1 MC3 -->|Miss| OS2

Tier 1: Edge Cache

Each edge server maintains its own local cache on NVMe SSD, with a hot cache in DRAM. Edge caches are intentionally small (2-4 TB per server) to ensure fast lookups. They store the most recently and most frequently accessed content for their geographic region. The edge cache is the first check — if content is found here, it is served with minimal latency (sub-millisecond lookup, no upstream network hops).

  • DRAM cache: ~10,000 objects (hot content). Lookup: ~100ns. Used for extremely popular content.
  • SSD cache: ~500,000 objects (warm content). Lookup: ~10-50μs. The primary cache tier.
  • Eviction policy: LRU with frequency-based promotion. Objects accessed more than 3 times are promoted to DRAM cache.

Tier 2: Mid-Tier (Shield) Cache

The mid-tier shield is a regional aggregation point with much larger cache capacity (100+ TB per shield). It sits between edge PoPs and origin servers. Its primary purpose is to reduce origin load — when content misses at one edge PoP, it might still be in the shield cache (having been fetched by a different edge PoP in the same region). This prevents N edge PoPs from sending N identical requests to origin for the same content.

Tier 3: Origin

The origin is the authoritative source of truth — the customer's actual server. Ideally, the origin should rarely see traffic, as the shield and edge layers should absorb the vast majority of requests. When origin is hit, the response is cached at both the shield and edge layers on the return path.

Cache Key Design

C#
public class CacheKeyBuilder
{
    public string BuildKey(CacheKeyLayout layout, HttpRequest request)
    {
        var builder = new ValueStringBuilder(stackalloc byte[1024]);

        // Include scheme, host, and path
        builder.Append(request.Scheme);
        builder.Append("://");
        builder.Append(request.Host);
        builder.Append(request.Path);

        if (layout.IncludeQueryString)
        {
            if (layout.NormalizeQueryString)
            {
                // Sort query parameters for normalization
                var sortedParams = request.Query
                    .OrderBy(p => p.Key, StringComparer.Ordinal)
                    .ThenBy(p => p.Value)
                    .ToList();

                builder.Append('?');
                for (int i = 0; i < sortedParams.Count; i++)
                {
                    if (i > 0) builder.Append('&');
                    builder.Append(sortedParams[i].Key);
                    builder.Append('=');
                    builder.Append(sortedParams[i].Value);
                }
            }
            else
            {
                builder.Append('?');
                builder.Append(request.RawQueryString);
            }
        }

        if (layout.IncludeHeaders?.Count > 0)
        {
            builder.Append('|');
            foreach (var header in layout.IncludeHeaders)
            {
                if (request.Headers.TryGetValue(header, out var value))
                {
                    builder.Append(header);
                    builder.Append('=');
                    builder.Append(value);
                    builder.Append(';');
                }
            }
        }

        if (layout.IncludeCookies?.Count > 0)
        {
            builder.Append('|');
            foreach (var cookie in layout.IncludeCookies)
            {
                if (request.Cookies.TryGetValue(cookie, out var value))
                {
                    builder.Append(cookie);
                    builder.Append('=');
                    builder.Append(value);
                    builder.Append(';');
                }
            }
        }

        // Return hex-encoded SHA-256 hash
        return Convert.ToHexString(
            SHA256.HashData(builder.AsSpan()));
    }
}

Cache Fill Patterns

Pattern Description Use Case
Pull (Lazy) Content is fetched from origin on first miss Default for most content; simplest approach
Push (Preload) Content is proactively pushed to edge before first request Large video files, software releases, predicted viral content
Revalidation Conditional requests (If-None-Match, If-Modified-Since) check freshness Content that changes rarely but must be fresh
Stale-While-Revalidate Serve stale content while asynchronously revalidating Content where slightly stale data is acceptable
Stale-If-Error Serve stale content if origin is unreachable Origin outage resilience
Performance Tip: The key metric for a cache hierarchy is the shield offload ratio — what percentage of origin requests does the shield absorb? A well-designed shield layer should achieve a 90%+ hit ratio, meaning only 10% of the traffic that misses at edge actually reaches origin. This is achieved by ensuring the shield has enough cache capacity to hold all content that is actively being requested across the region.

9. Cache Invalidation Strategies

Cache invalidation is one of the two hard problems in computer science (alongside naming). A CDN with stale content is worse than no CDN at all — security vulnerabilities, incorrect pricing, and outdated information can all result from serving stale cached content. The CDN must support multiple invalidation mechanisms with guaranteed delivery within seconds.

Invalidation Methods

  1. TTL-Based Expiration: The simplest and most common method. Each cached object has a Time-To-Live (TTL) set by origin headers (Cache-Control: max-age=3600) or CDN configuration rules. When TTL expires, the object is considered stale and revalidated on next access.
  2. Instant URL Purge: Customer requests invalidation of specific URLs. The CDN broadcasts a purge command to all edge PoPs, which remove the corresponding cache entries. Must complete globally within 5 seconds.
  3. Tag-Based Purge: Content is tagged at cache time with logical tags (e.g., product:12345). Purging a tag invalidates all content associated with that tag, regardless of URL. More flexible than URL purging.
  4. Wildcard/Prefix Purge: Invalidates all content matching a URL pattern (e.g., /images/products/*). Useful for bulk invalidation.
  5. Immediate (Zero-TTL): Content is never cached or revalidated on every request. Used for truly dynamic content that must always reflect the latest state.

Purge Propagation Architecture

graph TB CUSTOMER[Customer Dashboard / API] PURGE_API[Purge API Server] GLOBAL_QUEUE[Global Purge Queue - Kafka] REGIONAL_BROADCAST[Regional Pub/Sub - NATS] EDGE_NODE_1[Edge Node - NYC] EDGE_NODE_2[Edge Node - LHR] EDGE_NODE_3[Edge Node - NRT] EDGE_NODE_4[Edge Node - SYD] CUSTOMER -->|POST /purges| PURGE_API PURGE_API -->|Enqueue| GLOBAL_QUEUE GLOBAL_QUEUE -->|Fan-out| REGIONAL_BROADCAST REGIONAL_BROADCAST --> EDGE_NODE_1 REGIONAL_BROADCAST --> EDGE_NODE_2 REGIONAL_BROADCAST --> EDGE_NODE_3 REGIONAL_BROADCAST --> EDGE_NODE_4
C#
// Purge propagation system
public class PurgeOrchestrator
{
    private readonly KafkaProducer<PurgeCommand> _kafkaProducer;
    private readonly PurgeIndex _localIndex;

    public async Task<PurgeResult> ExecutePurge(PurgeRequest request)
    {
        var purgeRecord = new PurgeRecord
        {
            PurgeId = Guid.NewGuid().ToString(),
            ZoneId = request.ZoneId,
            Type = request.PurgeType,
            Targets = request.Targets,
            IssuedAt = DateTime.UtcNow,
            Status = PurgeStatus.Pending
        };

        // Step 1: Publish purge command to global Kafka topic
        var command = new PurgeCommand
        {
            PurgeId = purgeRecord.PurgeId,
            ZoneId = request.ZoneId,
            Type = request.PurgeType,
            Targets = request.Targets,
            IssuedAt = purgeRecord.IssuedAt
        };

        await _kafkaProducer.ProduceAsync(
            "cdn-purge-commands", command);

        // Step 2: Wait for acknowledgment from all edges
        var ackTimeout = TimeSpan.FromSeconds(5);
        var ackCount = await WaitForEdgeAcks(
            purgeRecord.PurgeId,
            expectedEdges: GetEdgeCount(),
            timeout: ackTimeout);

        purgeRecord.Status = PurgeStatus.Complete;
        purgeRecord.CompletedAt = DateTime.UtcNow;
        purgeRecord.AffectedEdges = ackCount;

        return new PurgeResult
        {
            PurgeId = purgeRecord.PurgeId,
            Status = PurgeStatus.Complete,
            EdgesAffected = ackCount,
            DurationMs = (purgeRecord.CompletedAt - purgeRecord.IssuedAt).TotalMilliseconds
        };
    }

    // Called on each edge node when a purge command arrives
    public async Task OnPurgeCommandReceived(PurgeCommand command)
    {
        switch (command.Type)
        {
            case PurgeType.URL:
                foreach (var url in command.Targets)
                {
                    var cacheKey = ComputeCacheKey(url);
                    _localIndex.Remove(cacheKey);
                }
                break;

            case PurgeType.Tag:
                foreach (var tag in command.Targets)
                {
                    var keys = _localIndex.GetKeysByTag(tag);
                    foreach (var key in keys)
                    {
                        _localIndex.Remove(key);
                    }
                }
                break;

            case PurgeType.Wildcard:
                var pattern = command.Targets[0];
                var matchingKeys = _localIndex.GetKeysByPattern(pattern);
                foreach (var key in matchingKeys)
                {
                    _localIndex.Remove(key);
                }
                break;

            case PurgeType.Prefix:
                var prefix = command.Targets[0];
                var prefixedKeys = _localIndex.GetKeysByPrefix(prefix);
                foreach (var key in prefixedKeys)
                {
                    _localIndex.Remove(key);
                }
                break;
        }

        // Send ACK back
        await SendPurgeAck(command.PurgeId);
    }
}
The Purge Consistency Problem: During the propagation window (0-5 seconds after a purge command), some edge nodes may still serve stale content. For most use cases (asset updates, content changes) this is acceptable. For security-critical purges (removing leaked credentials, revoking access), customers should use zero-TTL for the affected content before purging. This ensures no new content is cached while the purge propagates.

10. Origin Shield & Shielding

Origin shielding is one of the most impactful optimizations in a CDN. Without shielding, if content is not cached at an edge PoP, the edge server contacts the origin directly. When multiple edge PoPs simultaneously miss on the same content (e.g., during a cache stampede or when new content is first requested), they all bombard the origin with duplicate requests, potentially overwhelming it. This is known as the thundering herd problem.

How Shielding Works

A shield is a dedicated caching layer placed between edge PoPs and origin servers. There are typically 3-5 shields per continent. All cache misses from edge PoPs in a region are routed to the regional shield first. The shield checks its own larger cache — if the content is there, it serves it. If not, only the shield contacts the origin. This means that for a given piece of content, only one request per shield region ever reaches origin, regardless of how many edge PoPs are missing it.

graph TB subgraph "US East Region" E1[Edge: NYC] E2[Edge: DC] E3[Edge: Boston] end subgraph "Shield Layer" S1[Shield: US-East-1] end subgraph "Origin" O1[Origin Server] end E1 -->|Cache Miss| S1 E2 -->|Cache Miss| S1 E3 -->|Cache Miss| S1 S1 -->|Cache Miss| O1 S1 -->|Cache Hit| E1 S1 -->|Cache Hit| E2 S1 -->|Cache Hit| E3

Shield Selection Algorithm

C#
public class ShieldSelector
{
    private readonly Dictionary<string, List<ShieldNode>> _shieldsByRegion;
    private readonly HealthMonitor _healthMonitor;
    private readonly LoadBalancer _loadBalancer;

    public ShieldNode SelectShield(string edgePoPId, string originId)
    {
        // Find which region this edge PoP belongs to
        var edgeRegion = GetEdgeRegion(edgePoPId);
        var candidateShields = _shieldsByRegion[edgeRegion];

        // Filter to healthy shields
        var healthyShields = candidateShields
            .Where(s => _healthMonitor.IsHealthy(s.ShieldId))
            .ToList();

        if (healthyShields.Count == 0)
        {
            // Fallback: use any healthy shield globally
            healthyShields = _shieldsByRegion
                .SelectMany(kvp => kvp.Value)
                .Where(s => _healthMonitor.IsHealthy(s.ShieldId))
                .OrderBy(s => GetLatency(edgePoPId, s.ShieldId))
                .Take(2)
                .ToList();
        }

        // Select based on load and latency
        return _loadBalancer.SelectLeastLoaded(
            healthyShields,
            weights: s => new LoadWeight
            {
                LatencyWeight = 1.0 / (GetLatency(edgePoPId, s.ShieldId) + 1),
                LoadWeight = 1.0 - s.CurrentLoadRatio,
                CacheWeight = s.CacheHitRatio
            });
    }

    private int GetEdgeRegion(string edgePoPId)
    {
        // Edge PoP to region mapping
        return edgePoPId switch
        {
            "nyc" or "dc" or "bos" or "chi" or "atl" or "mia" => 0, // US-East
            "lax" or "sea" or "dfw" or "den" or "sjc" => 1,         // US-West
            "lhr" or "fra" or "ams" or "par" or "mad" => 2,         // EU
            "nrt" or "hnd" or "sin" or "hkg" or "icn" => 3,         // APAC
            "syd" or "mel" or "akl" => 4,                            // Oceania
            _ => 0
        };
    }
}

Cache Stampede Protection

Even with shielding, cache stampedes can occur at the shield layer when popular new content is first requested. The CDN uses several techniques to mitigate this:

  • Request coalescing: When multiple requests for the same cache key arrive simultaneously, only one actually fetches from origin. The others wait and receive the same response. This is implemented using a per-cache-key mutex/lock at the shield level.
  • Early expiration (Jitter): TTLs are reduced by a random jitter (e.g., 10%) so that all copies of the same content don't expire simultaneously, preventing synchronized cache misses.
  • Background refresh: Content that is frequently accessed is proactively revalidated in the background before its TTL expires, ensuring it is never stale.
C#
// Request coalescing to prevent cache stampedes
public class RequestCoalescer
{
    private readonly ConcurrentDictionary<string, Lazy<Task<OriginResponse>>>
        _inFlightRequests = new();

    public async Task<OriginResponse> CoalesceRequest(
        string cacheKey,
        Func<Task<OriginResponse>> originFetch)
    {
        var lazy = _inFlightRequests.GetOrAdd(
            cacheKey,
            _ => new Lazy<Task<OriginResponse>>(
                () => originFetch(),
                LazyThreadSafetyMode.ExecutionAndPublication));

        try
        {
            return await lazy.Value;
        }
        finally
        {
            // Remove from dictionary once complete
            _inFlightRequests.TryRemove(cacheKey, out _);
        }
    }
}
Impact of shielding: In production CDN deployments, origin shielding typically reduces origin requests by 60-90% compared to a flat (unshielded) architecture. This is especially pronounced for long-tail content that is cached at only a few edge PoPs — without shielding, each of those edge PoPs sends independent requests to origin.

11. TLS Termination & Certificate Management

Every HTTPS request passing through a CDN requires TLS termination at the edge. This involves performing the TLS handshake, decrypting the request, processing it, and encrypting the response — all without introducing significant latency. Modern CDNs handle millions of TLS handshakes per second across their global network.

TLS 1.3 and 0-RTT

TLS 1.3 (RFC 8446) significantly improves performance over TLS 1.2 by reducing the handshake from two round trips to one (1-RTT). For returning clients, TLS 1.3 supports 0-RTT resumption using Pre-Shared Keys (PSK), allowing the client to send encrypted data in the very first message without waiting for the handshake to complete. This is critical for CDN performance — the TLS handshake is often the largest single contributor to connection latency.

0-RTT Replay Risk: TLS 1.3 0-RTT data is vulnerable to replay attacks. An attacker who captures a 0-RTT client message can replay it against the server. For idempotent requests (GET, HEAD) this is generally safe, but for non-idempotent requests (POST, PUT, DELETE) it is dangerous. The CDN must either reject 0-RTT for non-idempotent methods or implement replay protection using session tickets with single-use tokens.

Certificate Management

Managing TLS certificates at CDN scale is a massive operational challenge. Cloudflare alone manages millions of certificates. The system must support:

  • Universal SSL: Automatic certificate provisioning for all customers using Let's Encrypt or internal CAs via the ACME protocol.
  • Custom certificates: Customers upload their own certificates, which are encrypted, distributed to all edge PoPs, and auto-renewed.
  • Advanced certificates: Wildcards, multi-domain (SAN), and dedicated IP certificates for enterprise customers.
  • Certificate transparency: All issued certificates are logged to public CT logs for auditability and to prevent misissuance.
C#
// ACME-based automatic certificate provisioning
public class CertificateProvisioner
{
    private readonly AcmeClient _acmeClient;
    private readonly CertificateStore _certStore;
    private readonly EdgeDistributor _distributor;

    public async Task ProvisionCertificate(string domain)
    {
        // Step 1: Create ACME order
        var order = await _acmeClient.CreateOrderAsync(new[]
        {
            new DnsIdentifier { Type = "dns", Value = domain }
        });

        // Step 2: Complete DNS-01 challenge
        foreach (var authz in order.Authorizations)
        {
            var challenge = authz.Challenges
                .First(c => c.Type == "dns-01");

            // Create _acme-challenge TXT record
            var txtValue = _acmeClient.ComputeKeyAuthorization(
                challenge.Token);

            await CreateDnsTxtRecord(
                $"_acme-challenge.{domain}",
                txtValue);

            // Wait for DNS propagation
            await Task.Delay(TimeSpan.FromSeconds(30));

            // Notify ACME server to validate
            await _acmeClient.CompleteChallengeAsync(challenge);
        }

        // Step 3: Generate CSR and finalize order
        var keyPair = GenerateEcdsaKeyPair(P256);
        var csr = GenerateCsr(domain, keyPair);
        var cert = await _acmeClient.FinalizeOrderAsync(
            order, csr);

        // Step 4: Store certificate encrypted
        var certId = await _certStore.StoreAsync(
            domain, cert, keyPair,
            encryptAtRest: true);

        // Step 5: Distribute to all edge PoPs
        await _distributor.DistributeCertificateAsync(
            certId,
            parallelism: 100,
            timeoutSeconds: 60);

        // Step 6: Schedule renewal (30 days before expiry)
        ScheduleRenewal(certId, cert.NotAfter.AddDays(-30));
    }
}

OCSP Stapling

To avoid the latency of clients contacting Certificate Authorities for OCSP (Online Certificate Status Protocol) validation, the CDN performs OCSP stapling. The edge server periodically fetches the OCSP response from the CA, caches it, and "staples" it to the TLS handshake. This eliminates an additional network round trip for the client and improves both privacy (the CA doesn't learn which clients are connecting) and performance.

Hardware Acceleration

At CDN scale, TLS operations consume significant CPU resources. Modern deployments use hardware acceleration to offload cryptographic operations:

  • AES-NI instructions: Hardware-accelerated AES encryption/decryption built into modern CPUs.
  • Intel QAT (QuickAssist Technology): Dedicated hardware for TLS acceleration, allowing a single server to handle 100,000+ TLS handshakes per second.
  • NVIDIA BlueField DPU: Smart NICs that offload TLS termination from the CPU entirely, freeing CPU cores for application logic.

12. DDoS Protection Architecture

DDoS protection is a core competency of any modern CDN. Attack volumes have grown exponentially — from gigabit-scale attacks in the 2010s to multi-terabit attacks today. The CDN's distributed architecture is its greatest defense asset: because the CDN operates in 300+ PoPs with aggregate bandwidth of 100+ Tbps, it can absorb attack traffic that would instantly overwhelm any single data center.

Attack Classification

Layer Attack Type Examples Mitigation
L3/L4 (Network) Volumetric UDP flood, DNS amplification, NTP amplification, Memcached amplification Anycast absorption, upstream filtering, BGP Flowspec
L4 (Transport) Protocol SYN flood, ACK flood, RST flood, fragmentation attacks SYN cookies, connection rate limiting, protocol validation
L7 (Application) Application-layer HTTP flood, Slowloris, RUDY, WordPress pingback, LDAP injection Behavioral analysis, JS challenges, CAPTCHA, rate limiting

Multi-Layer Defense Architecture

graph TB subgraph "Layer 1: Network Edge" BGP[BGP Flowspec - Drop at router] ANYCAST[Anycast Absorption] UPLINK[Uplink Filtering - ISP partnership] end subgraph "Layer 2: Packet Processing" SYN[SYN Cookie Validation] RATE[Connection Rate Limiting] PROTO[Protocol Validation] FRAG[Fragment Reassembly] end subgraph "Layer 3: HTTP Inspection" JS[JavaScript Challenge] CAPTCHA[CAPTCHA Challenge] WAF[WAF Rules] BOT[Bot Detection ML Model] BEHAV[Behavioral Analysis] end subgraph "Layer 4: Origin Protection" ORIGIN_RATE[Origin Rate Limiting] ORIGIN_WAF[Origin WAF] end ATTACK[DDoS Attack] --> BGP BGP --> ANYCAST ANYCAST --> UPLINK UPLINK --> SYN SYN --> RATE RATE --> PROTO PROTO --> FRAG FRAG --> JS JS --> CAPTCHA CAPTCHA --> WAF WAF --> BOT BOT --> BEHAV BEHAV --> ORIGIN_RATE

SYN Cookie Implementation

C#
// SYN cookie implementation for SYN flood protection
public class SynCookieValidator
{
    private readonly byte[] _secret;
    private readonly int _cookieBits = 24;
    private readonly TimeSpan _cookieWindow = TimeSpan.FromSeconds(60);

    public SynCookieValidator()
    {
        _secret = new byte[32];
        RandomNumberGenerator.Fill(_secret);
        // Rotate secret every 5 minutes
        _ = RotateSecretPeriodically();
    }

    public uint GenerateCookie(IPEndPoint client)
    {
        var timestamp = (uint)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 16);
        var data = CombineBytes(
            client.Address.GetAddressBytes(),
            BitConverter.GetBytes(timestamp));
        var hash = HMACSHA256.HashData(_secret, data);
        return BitConverter.ToUInt32(hash, 0) & ((1u << _cookieBits) - 1);
    }

    public bool ValidateCookie(IPEndPoint client, uint cookie)
    {
        // Check current and previous time window (clock skew tolerance)
        for (int offset = 0; offset <= 1; offset++)
        {
            var timestamp = (uint)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 16) - offset;
            var data = CombineBytes(
                client.Address.GetAddressBytes(),
                BitConverter.GetBytes(timestamp));
            var hash = HMACSHA256.HashData(_secret, data);
            var expectedCookie = BitConverter.ToUInt32(hash, 0)
                & ((1u << _cookieBits) - 1);

            if (cookie == expectedCookie)
                return true;
        }
        return false;
    }
}

Bot Detection with Machine Learning

Modern DDoS attacks increasingly use sophisticated bots that mimic human behavior — they execute JavaScript, maintain session cookies, and follow realistic browsing patterns. Simple fingerprinting (checking for missing headers, known bot user agents) is no longer sufficient. CDN providers deploy ML models that analyze:

  • Behavioral signals: Mouse movement patterns, scroll behavior, keystroke dynamics, click timing.
  • Network signals: IP reputation, ASN reputation, historical behavior, proxy/VPN detection.
  • Browser signals: Canvas fingerprint, WebGL fingerprint, WebRTC leaks, browser plugin enumeration.
  • Request patterns: Request timing distribution, resource loading order, navigation patterns.
The Arms Race: Bot operators continuously evolve their techniques to bypass detection. A static ruleset will be obsolete within weeks. The CDN must continuously retrain ML models on new attack data, maintain a global threat intelligence feed, and adapt challenge mechanisms (rotating JavaScript challenge implementations, varying CAPTCHA providers). This is an ongoing operational investment, not a one-time build.

13. Web Application Firewall (WAF)

The Web Application Firewall (WAF) inspects HTTP traffic at the edge for malicious patterns targeting web applications. Unlike DDoS protection (which focuses on volumetric and protocol attacks), the WAF focuses on application-layer attacks — SQL injection, cross-site scripting, remote code execution, path traversal, and other OWASP Top 10 vulnerabilities.

WAF Architecture

graph TB subgraph "Request Processing Pipeline" REQ[Incoming HTTP Request] PARSE[HTTP Parser] NORMALIZE[URI/Body Normalizer] RULES[Rule Engine] CUSTOM[Custom Rules] MANAGED[Managed Rulesets] RATE_L[Rate Limiting] CHALLENGE[Challenge Decision] RESP[Response] end REQ --> PARSE PARSE --> NORMALIZE NORMALIZE --> RULES RULES --> CUSTOM RULES --> MANAGED RULES --> RATE_L CUSTOM --> CHALLENGE MANAGED --> CHALLENGE RATE_L --> CHALLENGE CHALLENGE -->|Allow| RESP CHALLENGE -->|Block| BLOCK[Block Response] CHALLENGE -->|Challenge| JSCHAL[JS Challenge] CHALLENGE -->|Log| LOG[Security Event Log]

Rule Engine Design

C#
// WAF rule evaluation engine
public class WafRuleEngine
{
    private readonly List<WafRule> _rules;
    private readonly RegexPool _regexPool;
    private readonly RuleCompiler _compiler;

    public WafResult Evaluate(HttpRequest request)
    {
        var context = BuildEvaluationContext(request);

        foreach (var rule in _rules)
        {
            if (!rule.IsEnabled || !rule.AppliesToZone(request.ZoneId))
                continue;

            var matchResult = rule.Evaluate(context);
            if (matchResult.IsMatch)
            {
                // Log the security event
                LogSecurityEvent(new SecurityEvent
                {
                    RuleId = rule.RuleId,
                    Action = rule.Action,
                    ClientIP = request.ClientIP,
                    URI = request.Uri,
                    MatchedData = matchResult.MatchedData,
                    Timestamp = DateTime.UtcNow
                });

                switch (rule.Action)
                {
                    case WafAction.Block:
                        return WafResult.Blocked(rule);

                    case WafAction.Challenge:
                        return WafResult.Challenge(rule);

                    case WafAction.Simulate:
                        // Log but don't block (for testing rules)
                        continue;

                    case WafAction.ManagedChallenge:
                        return WafResult.ManagedChallenge(rule);
                }
            }
        }

        return WafResult.Allowed();
    }

    private EvaluationContext BuildEvaluationContext(
        HttpRequest request)
    {
        return new EvaluationContext
        {
            // URI components
            URI = request.Uri,
            URIPath = request.Path,
            URIQuery = request.QueryString,
            URIPathDecoded = Uri.UnescapeDataString(request.Path),
            URIQueryDecoded = Uri.UnescapeDataString(request.QueryString),

            // Headers
            UserAgent = request.Headers.GetValueOrDefault("User-Agent"),
            Referer = request.Headers.GetValueOrDefault("Referer"),
            AcceptLanguage = request.Headers.GetValueOrDefault("Accept-Language"),
            AllHeaders = request.Headers,

            // Body (for POST/PUT)
            RequestBody = request.HasBody ? request.Body : null,
            RequestBodyLength = request.ContentLength ?? 0,

            // Client info
            ClientIP = request.ClientIP,
            ClientCountry = request.ClientCountry,
            ClientASN = request.ClientASN,
            HTTPMethod = request.Method,
            HTTPVersion = request.HttpVersion,

            // Pre-computed fields for fast rule evaluation
            URILength = request.Uri.Length,
            QueryParamCount = request.Query.Count,
            HeaderCount = request.Headers.Count,
            BodyMD5 = request.HasBody ?
                Convert.ToHexString(MD5.HashData(request.Body)) : null
        };
    }
}

OWASP Top 10 Rule Examples

Vulnerability Detection Pattern Typical Action
SQL Injection Pattern match on URI params, body, headers for SQL keywords (UNION SELECT, OR 1=1, etc.) Block
Cross-Site Scripting (XSS) Detect <script>, event handlers (onerror=), JavaScript URIs Block
Path Traversal Detect ../, %2e%2e%2f, ..%252f (double encoding) Block
Remote Code Execution Detect eval(, exec(, command injection patterns Block
Session Fixation Detect session ID in URL parameters Challenge
Bot Traffic ML model classification based on behavioral analysis Challenge
Rule Compilation for Performance: WAF rules must be evaluated in under 1 millisecond to avoid impacting request latency. The rule compiler converts human-readable rules into optimized bytecode or native machine code. Regex patterns are compiled using deterministic finite automata (DFA) to ensure O(n) matching time relative to input length. Complex rules that require multiple passes over the request are pre-computed where possible (e.g., building a hash table of all parameter names during the normalization step).

14. Load Balancing at the Edge

Load balancing in a CDN operates at multiple levels: balancing traffic across edge servers within a PoP, balancing origin requests across origin servers, and balancing traffic across multiple origin shields. Each level uses different algorithms and health checking mechanisms.

Intra-PoP Load Balancing

Within a single edge PoP, incoming traffic is distributed across the server fleet. The distribution mechanism depends on the network architecture:

  • Hardware load balancer (F5/A10): Traditional approach for Tier 1 PoPs. Handles 40+ Gbps per device, provides SSL offloading, and supports sophisticated health checks.
  • ECMP (Equal-Cost Multi-Path): Multiple servers share the same virtual IP (VIP) via BGP ECMP. Packets are distributed across servers based on flow hash. Stateless and extremely high performance, but uneven distribution is possible.
  • DSR (Direct Server Return): Load balancer only handles inbound traffic; responses bypass the load balancer and go directly to the client. Reduces load balancer bottleneck.
C#
// Origin load balancer with health checks and failover
public class OriginLoadBalancer
{
    private readonly List<OriginTarget> _origins;
    private readonly HealthChecker _healthChecker;
    private readonly CircuitBreaker _circuitBreaker;

    public OriginLoadBalancer(List<OriginTarget> origins)
    {
        _origins = origins;
        _healthChecker = new HealthChecker(
            interval: TimeSpan.FromSeconds(5),
            timeout: TimeSpan.FromSeconds(3),
            unhealthyThreshold: 3,
            healthyThreshold: 2);
        _circuitBreaker = new CircuitBreaker(
            failureThreshold: 5,
            recoveryTimeout: TimeSpan.FromSeconds(30));
    }

    public async Task<OriginTarget> SelectOrigin(
        string zoneId, string contentKey)
    {
        var healthyOrigins = _origins
            .Where(o => o.ZoneId == zoneId)
            .Where(o => _healthChecker.IsHealthy(o.OriginId))
            .Where(o => !_circuitBreaker.IsOpen(o.OriginId))
            .OrderBy(o => o.CurrentConnections)
            .ThenBy(o => o.Weight)  // Lower weight = higher priority
            .ToList();

        if (healthyOrigins.Count == 0)
        {
            // All origins unhealthy: open circuit breaker and try
            // the least-recently-failed origin
            return _origins
                .Where(o => o.ZoneId == zoneId)
                .OrderBy(o => o.LastFailureTime)
                .First();
        }

        // Weighted round-robin among healthy origins
        return WeightedRoundRobin(healthyOrigins);
    }

    // Active health checking with TCP and HTTP probes
    private async Task RunHealthCheck(OriginTarget origin)
    {
        while (true)
        {
            try
            {
                using var client = new TcpClient();
                var connectTask = client.ConnectAsync(
                    origin.Address, origin.Port);
                var timeout = Task.Delay(_healthChecker.Timeout);
                var completed = await Task.WhenAny(connectTask, timeout);

                if (completed == timeout || !client.Connected)
                {
                    _healthChecker.RecordFailure(origin.OriginId);
                    continue;
                }

                // TCP health check passed; do HTTP check
                var httpClient = new HttpClient
                {
                    BaseAddress = new Uri(
                        $"https://{origin.Address}:{origin.Port}"),
                    Timeout = _healthChecker.Timeout
                };

                var response = await httpClient.GetAsync(
                    origin.HealthCheckPath);

                if ((int)response.StatusCode >= 500)
                {
                    _healthChecker.RecordFailure(origin.OriginId);
                }
                else
                {
                    _healthChecker.RecordSuccess(origin.OriginId);
                    _circuitBreaker.RecordSuccess(origin.OriginId);
                }
            }
            catch (Exception)
            {
                _healthChecker.RecordFailure(origin.OriginId);
            }

            await Task.Delay(_healthChecker.Interval);
        }
    }
}

Circuit Breaker Pattern

The circuit breaker prevents the CDN from repeatedly hammering a failing origin. When the failure count for an origin exceeds the threshold, the circuit "opens" — requests are immediately failed or served from stale cache for the configured recovery period. After the recovery period, the circuit enters a "half-open" state, allowing a single probe request. If it succeeds, the circuit closes; if it fails, it opens again.

Graceful Degradation: When all origin servers are down, the CDN should serve stale cached content (controlled by the stale-if-error directive). This provides graceful degradation — users see slightly outdated content rather than an error page. The maximum staleness period is configurable by the customer and defaults to 1 day.

15. Edge Computing & Serverless@Edge

Edge computing has transformed CDNs from passive caching layers into active compute platforms. Platforms like Cloudflare Workers, Fastly Compute, and Deno Deploy allow customers to run JavaScript, TypeScript, Rust, and even C++ code directly at the edge — in every PoP, within milliseconds of end users. This enables use cases like A/B testing, authentication, API gateway logic, personalization, and real-time content transformation, all without routing traffic back to a central origin.

Isolation Model

Edge compute isolates customer code using WebAssembly (Wasm) or V8 isolates (not full VMs or containers). This provides:

  • Cold start < 1ms: V8 isolates are lightweight enough to create and destroy per-request if needed.
  • Memory isolation: Each isolate has a strict memory limit (typically 128MB).
  • CPU time limits: Request handlers have strict CPU time limits (typically 10-50ms).
  • Network isolation: Customer code can only access allowed destinations (origin servers, KV stores).
C#
// Edge function example: A/B testing at the edge
public class AbTestingEdgeFunction
{
    // Configuration stored in KV store, updated by control plane
    private readonly AbTestConfig _config;

    public async Task<EdgeResponse> HandleRequest(
        EdgeRequest request)
    {
        // Check for existing A/B test cookie
        var existingBucket = request.Cookies
            .GetValueOrDefault("ab-test");

        string bucket;
        if (!string.IsNullOrEmpty(existingBucket) &&
            _config.Buckets.ContainsKey(existingBucket))
        {
            bucket = existingBucket;
        }
        else
        {
            // Assign to bucket based on consistent hashing
            bucket = AssignBucket(request.ClientIP, _config.TestId);
        }

        var bucketConfig = _config.Buckets[bucket];

        // Rewrite URL to bucket-specific origin or path
        var modifiedRequest = request with
        {
            URI = bucketConfig.PathRewrite(request.URI),
            Headers = request.Headers
                .Set("X-AB-Bucket", bucket)
                .Set("X-AB-Test", _config.TestId)
        };

        // Fetch from origin
        var response = await FetchFromOrigin(modifiedRequest);

        // Add A/B test bucket to response
        return response with
        {
            Headers = response.Headers
                .SetCookie($"ab-test={bucket}; " +
                    $"Path=/; Max-Age=86400; HttpOnly"),
            Headers = response.Headers
                .Set("X-AB-Bucket", bucket)
        };
    }

    private string AssignBucket(string clientId, string testId)
    {
        // Consistent hashing ensures same user always
        // gets same bucket
        var hash = SHA256.HashData(
            Encoding.UTF8.GetBytes($"{clientId}:{testId}"));
        var hashInt = BitConverter.ToUInt32(hash, 0);
        var bucketIndex = hashInt % (uint)_config.Buckets.Count;
        return _config.Buckets.Keys.ElementAt((int)bucketIndex);
    }
}

Edge KV Storage

Edge functions often need access to configuration data, feature flags, or small datasets. Edge KV stores provide eventually-consistent key-value storage that is replicated to every edge PoP. Reads are served locally (sub-millisecond), while writes propagate globally within 30-60 seconds.

Edge Compute Use Cases

Use Case Description Latency Benefit
A/B Testing Route users to experiment variants based on cookies/headers Eliminates origin round trip for bucket assignment
Authentication Validate JWTs, check session cookies, enforce access control Auth decisions in <1ms instead of 50-100ms origin round trip
Image Optimization Resize, compress, and convert images on-the-fly Edge processing eliminates origin compute load
API Gateway Route, authenticate, rate-limit, and transform API requests Reduces API latency for geographically distributed clients
Geolocation-based Content Serve different content based on user's country/city Content decision at edge without origin lookup
Bot Detection Run JS challenges and behavioral analysis at edge Challenge issued immediately without waiting for origin
Limitations: Edge compute has strict resource constraints. CPU time limits (10-50ms), memory limits (128MB), no filesystem access, limited network access, and no persistent local state. Applications requiring large databases, long-running processes, or heavy computation must still use origin-based compute. Edge compute is best suited for lightweight, request-scoped logic that benefits from proximity to the user.

16. Real-Time Analytics & Logging

CDN analytics serve two audiences: customers (who need visibility into their own traffic, cache performance, and security events) and operations (who need fleet-wide monitoring, capacity planning, and incident detection). The analytics pipeline must handle enormous throughput — billions of log events per day — while providing sub-second query latency for real-time dashboards.

Data Pipeline Architecture

graph LR subgraph "Edge Layer (300 PoPs)" E1[Edge Server 1] E2[Edge Server 2] EN[Edge Server N] end subgraph "Ingestion Layer" KAFKA[Kafka Cluster - Global] KAFKA2[Kafka Regional Topics] end subgraph "Processing Layer" FLINK[Flink Stream Processing] AGGREGATOR[1-Minute Aggregator] ANOMALY[Anomaly Detection] end subgraph "Storage Layer" CLICK[ClickHouse Cluster] TSDB[TimescaleDB] S3[S3 - Long-term Storage] end subgraph "Serving Layer" API[Analytics API] GRAFANA[Grafana Dashboards] ALERT[Alert Manager] end E1 -->|Async log batch| KAFKA2 E2 -->|Async log batch| KAFKA2 EN -->|Async log batch| KAFKA2 KAFKA2 --> KAFKA KAFKA --> FLINK FLINK --> AGGREGATOR FLINK --> ANOMALY AGGREGATOR --> CLICK AGGREGATOR --> TSDB FLINK --> S3 CLICK --> API TSDB --> API API --> GRAFANA ANOMALY --> ALERT
C#
// Edge-side log batching and async shipping
public class EdgeLogShipper
{
    private readonly Channel<RequestLogEntry> _logChannel;
    private readonly KafkaProducer<LogBatch> _kafkaProducer;
    private readonly Timer _flushTimer;
    private readonly int _batchSize = 1000;
    private readonly TimeSpan _flushInterval = TimeSpan.FromSeconds(1);

    public EdgeLogShipper(KafkaProducer<LogBatch> producer)
    {
        _kafkaProducer = producer;
        _logChannel = Channel.CreateBounded<RequestLogEntry>(
            new BoundedChannelOptions(100_000)
            {
                FullMode = BoundedChannelFullMode.DropOldest,
                SingleReader = true
            });

        _flushTimer = new Timer(
            async _ => await FlushBatch(),
            null,
            _flushInterval,
            _flushInterval);

        _ = ConsumeChannel();
    }

    public void LogRequest(RequestLogEntry entry)
    {
        // Non-blocking write; drop if channel is full
        _logChannel.Writer.TryWrite(entry);
    }

    private async Task ConsumeChannel()
    {
        var batch = new List<RequestLogEntry>(_batchSize);

        await foreach (var entry in _logChannel.Reader.ReadAllAsync())
        {
            batch.Add(entry);

            if (batch.Count >= _batchSize)
            {
                await FlushBatch();
                batch.Clear();
            }
        }
    }

    private async Task FlushBatch()
    {
        var batch = new LogBatch
        {
            EdgeNodeId = Environment.MachineName,
            Timestamp = DateTime.UtcNow,
            Entries = Interlocked.Exchange(
                ref _currentBatch, new List<RequestLogEntry>())
        };

        if (batch.Entries.Count > 0)
        {
            await _kafkaProducer.ProduceAsync(
                $"cdn-logs-{batch.Entries[0].ZoneId}",
                batch);
        }
    }
}

Real-Time Anomaly Detection

The analytics pipeline includes an anomaly detection system that monitors traffic patterns in real-time. It detects:

  • Traffic spikes: Sudden increases in request rate (may indicate DDoS or viral content).
  • Cache hit ratio drops: Sudden decrease in cache hit ratio may indicate a cache configuration issue or an attack with unique URIs.
  • Origin latency spikes: Increased origin response times may indicate origin overload or network issues.
  • Error rate increases: Sudden increases in 5xx errors may indicate upstream failures.
  • Geographic anomalies: Unexpected traffic patterns from unusual geographies may indicate targeted attacks.
Observability Best Practice: Every request handled by the CDN should produce a unique RayId that is included in response headers and propagated through the entire request path (edge → shield → origin). This allows customers and support teams to trace a specific request through the entire system for debugging. Cloudflare pioneered this pattern, and it has become an industry standard.

17. Multi-CDN Strategy

Large enterprises and media companies increasingly use multiple CDN providers simultaneously — a practice known as Multi-CDN. This approach improves reliability (if one CDN goes down, traffic shifts to another), performance (each CDN may have different strengths in different regions), and negotiating leverage (avoiding vendor lock-in). Designing a multi-CDN architecture introduces additional complexity in traffic management, failover, and observability.

Multi-CDN Traffic Management

graph TB subgraph "Traffic Management Layer" GSLB[Global Server Load Balancer] DNS_PROVIDER[DNS Provider with Steering] MONITOR[Real-Time Performance Monitor] end subgraph "CDN Providers" CDN1[CDN Provider A - Primary] CDN2[CDN Provider B - Secondary] CDN3[CDN Provider C - Tertiary] end subgraph "Origin" ORIGIN[Customer Origin] end GSLB -->|DNS steering| CDN1 GSLB -->|DNS steering| CDN2 GSLB -->|DNS steering| CDN3 CDN1 -->|Cache Miss| ORIGIN CDN2 -->|Cache Miss| ORIGIN CDN3 -->|Cache Miss| ORIGIN MONITOR -->|Performance data| GSLB

CDN Selection Strategies

Strategy Description Pros Cons
Primary/Secondary All traffic to primary; failover to secondary on outage Simple; predictable cost Wastes secondary capacity; slow failover
Weighted Split Traffic split 70/30 or 80/20 across providers Better utilization; reduces single-provider risk Complex cost modeling; cache duplication
Geo-Based Different CDNs for different regions Optimizes per-region performance Complex DNS; regional failover limited
Real-Time Steering DNS steers based on real-time performance metrics Best performance; automatic failover Most complex; requires monitoring infrastructure

Real-Time CDN Steering

C#
// Multi-CDN real-time steering engine
public class CdnSteeringEngine
{
    private readonly Dictionary<string, CdnProvider> _providers;
    private readonly PerformanceMonitor _monitor;
    private readonly DnsManager _dns;

    public async Task<SteeringDecision> SelectCdn(
        string clientIp, string requestUri)
    {
        var clientGeo = _geoDb.Lookup(clientIp);
        var decisions = new List<CdnScore>();

        foreach (var provider in _providers.Values)
        {
            // Get real-time performance metrics for this provider
            // in the client's region
            var metrics = _monitor.GetMetrics(
                provider.Id, clientGeo.Region);

            var score = new CdnScore
            {
                ProviderId = provider.Id,
                // Weighted scoring model
                Score =
                    (metrics.CacheHitRatio * 0.3) +
                    ((1.0 / Math.Max(metrics.AvgLatencyMs, 1)) * 0.3) +
                    ((1.0 - metrics.ErrorRate) * 0.2) +
                    ((1.0 - metrics.OriginLatencyMs / 1000) * 0.2),

                LatencyMs = metrics.AvgLatencyMs,
                CacheHitRatio = metrics.CacheHitRatio,
                ErrorRate = metrics.ErrorRate,
                IsHealthy = metrics.ErrorRate < 0.05
                    && metrics.AvgLatencyMs < 200
            };

            decisions.Add(score);
        }

        // Select best provider, excluding unhealthy ones
        var selected = decisions
            .Where(d => d.IsHealthy)
            .OrderByDescending(d => d.Score)
            .FirstOrDefault();

        // Update DNS to direct future traffic
        await _dns.UpdateSteeringRecord(
            requestUri, selected.ProviderId,
            ttl: 30); // Short TTL for quick failover

        return new SteeringDecision
        {
            ProviderId = selected.ProviderId,
            Scores = decisions,
            Reason = $"Score: {selected.Score:F3}"
        };
    }
}
Cache Duplication Cost: The biggest cost of multi-CDN is that content is cached independently at each CDN provider. If your total unique content is 10 TB and you use 3 CDNs, you are paying for 30 TB of cache storage and the associated bandwidth. This cost must be weighed against the reliability and performance benefits. For most customers, a single-CDN architecture with strong SLAs is more cost-effective.

18. Reliability & Failure Modes

CDN reliability is paramount — a CDN outage affects every website and application that depends on it. The 2021 Fastly outage lasted only 49 minutes but took down Reddit, Amazon, The Guardian, and the New York Times. The 2022 Cloudflare outage (though brief) demonstrated that even the most resilient CDNs are not immune to configuration errors. Designing for reliability requires understanding failure modes and building appropriate safeguards.

Failure Mode Analysis

Failure Mode Impact Detection Mitigation
PoP network partition Users routed to partitioned PoP see errors BGP route withdrawal; health checks fail Anycast failover; BGP withdrawal routes traffic to healthy PoPs
Edge server crash Requests handled by failed server are dropped Process monitor; health endpoint Load balancer removes server; restart via process manager
Cache corruption Garbage content served; potential security risk Content hash validation; checksum verification Content integrity checks at cache store/retrieve; quarantine corrupted entries
Origin failure Cache misses result in 5xx errors Health checks; error rate monitoring Circuit breaker; serve stale (stale-if-error); failover to backup origin
DNS failure Users cannot resolve CDN domain; site becomes unreachable DNS monitoring from multiple vantage points Multiple DNS providers; anycast DNS; short TTLs; DNSSEC
Certificate expiry TLS errors; browsers reject connections Certificate monitoring; expiry alerts Automated renewal; certificate transparency monitoring; early renewal
Config push failure Some PoPs run stale configuration Config version tracking; edge sync monitoring Gradual rollout; config version comparison; rollback capability
DDoS exhaustion Legitimate traffic degraded during attack Traffic anomaly detection; latency monitoring Over-provisioned capacity; upstream ISP filtering; scrubbing centers

Bulkhead Pattern

C#
// Bulkhead isolation to prevent cascade failures
public class BulkheadIsolatedRequestHandler
{
    private readonly SemaphoreSlim _generalSlot;
    private readonly SemaphoreSlim _premiumSlot;
    private readonly SemaphoreSlim _apiSlot;
    private readonly OriginLoadBalancer _origin;

    public BulkheadIsolatedRequestHandler(int maxConcurrent = 10000)
    {
        // Partition capacity across customer tiers
        _generalSlot = new SemaphoreSlim(
            (int)(maxConcurrent * 0.5));  // 50% for general
        _premiumSlot = new SemaphoreSlim(
            (int)(maxConcurrent * 0.3));  // 30% for premium
        _apiSlot = new SemaphoreSlim(
            (int)(maxConcurrent * 0.2));  // 20% for API
    }

    public async Task<EdgeResponse> HandleRequest(
        EdgeRequest request)
    {
        var semaphore = GetBulkhead(request.PlanTier);

        if (!await semaphore.WaitAsync(TimeSpan.FromSeconds(5)))
        {
            return EdgeResponse.ServiceUnavailable(
                "Server capacity temporarily exceeded");
        }

        try
        {
            return await ProcessRequest(request);
        }
        finally
        {
            semaphore.Release();
        }
    }

    private SemaphoreSlim GetBulkhead(PlanTier tier) => tier switch
    {
        PlanTier.Premium => _premiumSlot,
        PlanTier.Enterprise => _premiumSlot,
        PlanTier.API => _apiSlot,
        _ => _generalSlot
    };
}

Chaos Engineering

Proactive reliability testing through chaos engineering is essential for CDNs. The practice involves deliberately injecting failures into the production system to verify that automated recovery mechanisms work correctly:

  • Edge server kills: Randomly terminate edge server processes to verify load balancer failover works within seconds.
  • Network partitions: Use tc netem to simulate network partitions between PoPs to verify graceful degradation.
  • Origin unavailability: Block traffic to specific origin servers to verify circuit breaker and stale-serve behavior.
  • Certificate invalidation: Revoke a certificate to verify automated renewal completes before expiry.
  • Config corruption: Push invalid configurations to verify validation catches them before they reach production.
Worst-Case Scenario: Configuration Error Cascading
The most dangerous CDN failures are not hardware failures (which anycast handles gracefully) but configuration errors that propagate to all PoPs simultaneously. A misconfigured WAF rule that blocks all traffic, or a cache configuration that bypasses all content, can take down every customer instantly. Mitigation requires: staged rollouts (1% → 10% → 50% → 100% of PoPs), automated canary testing, instant rollback capability, and a "kill switch" configuration that bypasses all CDN logic and serves directly from origin.

19. Cost Estimation & Infrastructure Sizing

Operating a CDN is capital-intensive. The cost structure is dominated by bandwidth (which is often the most expensive line item), followed by hardware, real estate, power, and personnel. Understanding the cost model is essential for both building a CDN business and for estimating the cost of CDN services as a customer.

Cost Breakdown

Cost Category Percentage of Total Notes
Bandwidth (transit + peering) 35-45% Largest cost; decreases with scale due to peering discounts
Hardware (servers, switches, NICs) 20-25% 3-4 year depreciation cycle; NVMe SSDs are a significant cost
Data center (colocation, power, cooling) 15-20% Varies wildly by location; Tier 1 metros most expensive
Personnel (engineering, ops, support) 10-15% 24/7 SRE teams across time zones; specialized networking engineers
Software licenses & tools 3-5% Monitoring, security scanning, development tools
Compliance & certifications 1-2% SOC 2, PCI DSS, ISO 27001 audits

Infrastructure Sizing for 300 PoPs

Component Per PoP (Avg) Total (300 PoPs) Unit Cost Total Cost
Edge Servers 50 15,000 $15,000 $225M
NVMe SSDs 200 60,000 $500 $30M
Network Switches 5 1,500 $30,000 $45M
Bandwidth (annual) ~10 Gbps avg 3,000 Gbps $20/Mbps/mo $720M/year
Colocation (annual) 10 racks 3,000 racks $8,000/rack/yr $24M/year
Engineering Team 500 engineers $200K avg $100M/year
Revenue Context: Cloudflare generated approximately $1.6 billion in annual revenue (2024) with 310+ PoPs. Akamai generated $3.8 billion with 4,000+ PoPs. The revenue per PoP varies dramatically — premium metro locations generate far more revenue than small-market deployments. The key to profitability is maximizing the revenue per unit of bandwidth and hardware, which requires attracting high-value enterprise customers.

Unit Economics

  • Bandwidth cost per GB: $0.005-0.02 depending on volume and peering agreements. Large CDNs negotiate rates of $0.002-0.005/GB through extensive peering.
  • Server cost per request: A $15,000 server handling 500K req/s over 3 years costs approximately $0.01 per million requests.
  • Storage cost per GB/month: $0.02-0.05 for NVMe SSD in a colocation facility.
  • Purge cost: Essentially free — purge propagation uses existing pub/sub infrastructure and negligible bandwidth.

Cost Optimization Strategies

  • Peering agreements: Establish direct peering with major ISPs to avoid transit costs. At sufficient scale, peering is free or reciprocal.
  • Hardware optimization: Use custom server designs (like Open Compute Project) to reduce hardware costs by 20-30%.
  • Cache efficiency: Higher cache hit ratios directly reduce bandwidth costs. Investing in better caching algorithms has a direct ROI.
  • Location optimization: Not every PoP needs the same hardware. Tier 3 locations can use smaller, cheaper servers.
  • Power efficiency: Use ARM-based servers for edge workloads — they offer 30-40% better performance per watt for cache-heavy workloads.

20. Interview Q&A Deep Dive

Q1: How would you handle a cache stampede when a popular piece of content goes viral?

Answer: A cache stampede occurs when cached content expires simultaneously and multiple concurrent requests all attempt to fetch from origin. The solution combines three techniques: (1) Request coalescing — use a per-cache-key lock so only one request fetches from origin while others wait for the response; (2) TTL jitter — randomize TTLs by 10-20% so content doesn't expire in synchronized waves; (3) Background revalidation — for hot content, proactively refresh the cache before expiry rather than waiting for the first miss. At the shield layer, these techniques are especially important because a single shield may serve dozens of edge PoPs, amplifying the stampede effect.

Q2: How do you achieve sub-5-second global purge across 300+ PoPs?

Answer: Global purge uses a push-based architecture (not pull). The purge command is published to a global message queue (Kafka), which fans out to regional NATS clusters. Each edge server subscribes to its regional NATS topic and processes purge commands asynchronously. The purge index at each edge server is an in-memory hash map mapping cache keys to invalidation timestamps — on lookup, stale entries are discarded. The 5-second target is achievable because: (1) Kafka delivery is typically sub-second globally; (2) NATS fan-out is sub-second within a region; (3) local index invalidation is O(1) hash map removal. The ACK mechanism confirms completion across all edges within the timeout window.

Q3: How do you handle the thundering herd problem when origin shield fails?

Answer: When a shield fails, edge PoPs must failover to another shield or directly to origin. The risk is that many edge PoPs simultaneously switch to a different shield, overwhelming it. Mitigation: (1) Staggered failover — each edge PoP waits a random jitter (0-5 seconds) before failing over, preventing synchronized swarming; (2) Direct-to-origin limiting — if edges bypass shields, they apply aggressive rate limiting (max 10 req/s per unique URL per edge) to prevent overwhelming origin; (3) Stale serve — edges serve stale cached content while the failover happens, reducing the miss rate.

Q4: Explain the trade-offs between cache hit ratio and content freshness.

Answer: This is a fundamental CDN trade-off. Longer TTLs improve cache hit ratio (fewer origin fetches, lower latency, lower bandwidth cost) but increase staleness. Shorter TTLs improve freshness but increase origin load and latency. The solution is a multi-tiered approach: (1) Content-type based TTLs — static assets (images, CSS, JS) get long TTLs (1 week); API responses get short TTLs (60s); HTML gets medium TTLs (5 minutes); (2) Stale-while-revalidate — serve cached content immediately while asynchronously revalidating in the background, giving both freshness and performance; (3) Tag-based purging — cache content indefinitely but use instant tag-based purging for on-demand invalidation, combining maximum hit ratio with instant freshness when needed.

Q5: How would you design TLS termination to handle 50 million TLS handshakes per second?

Answer: At this scale, every optimization matters. Key techniques: (1) TLS 1.3 with 0-RTT — returning clients complete the handshake with zero additional round trips using PSK; (2) Session ticket caching — cache session tickets at edge servers to enable fast resumption; (3) Hardware acceleration — use Intel QAT or NVIDIA BlueField DPUs to offload crypto operations from CPU; (4) Connection coalescing — use HTTP/2 and HTTP/3 connection pooling to minimize new TLS handshakes; (5) OCSP stapling — pre-fetch OCSP responses and staple them, eliminating client-side OCSP round trips. With these optimizations, a single modern server can handle 50,000+ TLS handshakes per second, and 1,000 servers across 300 PoPs handle the 50M target.

Q6: How do you detect and mitigate a DNS hijacking attack against your CDN?

Answer: DNS hijacking redirects users to malicious servers by compromising DNS infrastructure. Defense layers: (1) DNSSEC — sign all DNS records with DNSSEC to prevent forgery; (2) Registry locks — enable registrar-level locks on domain names to prevent unauthorized transfers; (3) Anomaly detection — monitor for unexpected changes in DNS resolution patterns (e.g., sudden increase in resolution failures, or traffic appearing to come from unexpected geographies); (4) Certificate Transparency monitoring — monitor CT logs for unauthorized certificate issuance for customer domains; (5) Always-use-HTTPS — enforce HTTPS via HSTS preloading so even if DNS is hijacked, the browser won't connect without valid TLS.

Q7: Design a CDN for video streaming specifically — what's different from general HTTP CDN?

Answer: Video CDN has unique requirements: (1) Large objects — video files are 1-100 GB, requiring different caching strategies (prefill/push rather than pull); (2) Sequential access patterns — video segments are requested in order, enabling predictive prefetching; (3) Byte-range requests — seeking requires serving partial content, complicating cache key design (must cache by range); (4) Adaptive bitrate — HLS/DASH manifests must be generated dynamically and cached with short TTLs; (5) Origin shield optimization — video CDN uses fewer, larger shields with very high storage capacity; (6) Peering optimization — video bandwidth is enormous, so extensive ISP peering is critical to avoid transit costs.

Q8: How do you handle a zero-day vulnerability in your edge server software across 300 PoPs?

Answer: Zero-day response requires: (1) Instant feature flag — ability to disable the vulnerable feature globally within seconds using the control plane; (2) WAF virtual patching — deploy a WAF rule that blocks exploitation attempts at the edge, providing immediate protection while the patch is developed; (3) Canary deployment — push the patch to 1% of PoPs first, monitor for regressions, then roll out to 10%, 50%, 100% over 30 minutes; (4) BGP failover as last resort — if the vulnerability is catastrophic and unpatchable quickly, withdraw BGP routes for affected PoPs, forcing traffic to unaffected PoPs; (5) Immutable infrastructure — edge servers run immutable images; patches require a full image rebuild and rolling replacement, not in-place updates.

Q9: How would you implement cache normalization to maximize hit ratios?

Answer: Cache normalization ensures semantically equivalent requests produce the same cache key, preventing duplicate caching. Techniques: (1) Query string sorting?b=2&a=1 and ?a=1&b=2 produce the same key; (2) Query string stripping — remove tracking parameters (utm_source, fbclid) that don't affect content; (3) Case normalization — lowercase hostname and path; (4) Encoding normalization — decode percent-encoded characters where safe (%20+); (5) Default document/index.html and / serve the same content; (6) Protocol normalization — HTTP and HTTPS can share cache if origin content is identical. Each normalization step must be configurable per-zone, as some customers need to differentiate by these fields.

Q10: How do you measure and optimize CDN performance for a customer?

Answer: Performance measurement covers: (1) Cache hit ratio — the percentage of requests served from cache vs. origin. Target: 95%+ for static assets. Optimization: longer TTLs, better cache key normalization, preloading; (2) Time-to-first-byte (TTFB) — measures edge latency. Target: <50ms. Optimization: ensure edge PoP is close to users; check NIC/CPU bottlenecks; (3) Edge response time — end-to-end processing time at edge. Target: <10ms for cache hits. Optimization: profile the request pipeline; optimize WAF rules; reduce regex complexity; (4) Origin response time — time to fetch from origin. Target: <200ms. Optimization: origin server tuning, database optimization; (5) Bandwidth saved — bytes served from cache vs. bytes that would have been served from origin. This directly translates to cost savings.
Interview Strategy: CDN design questions allow you to demonstrate breadth across networking (BGP, anycast, DNS, TLS), distributed systems (caching, consistency, failure modes), security (DDoS, WAF, certificates), and systems engineering (hardware, observability, capacity planning). Start with the high-level architecture, then drill into the specific area the interviewer asks about. Always discuss trade-offs — there is no universally correct CDN design, only designs that optimize for different constraints.

CDN System Design — Senior+ Guide