← Back to posts
comparison guide self-hosted · · 16 min read

Self-Hosted CDN and Edge Caching: Varnish, Traffic Server, Squid, and Nginx Guide 2026

Build your own CDN with open-source reverse proxy caching. Compare Varnish Cache, Apache Traffic Server, Squid, and Nginx for self-hosted edge caching in 2026.

OS
Editorial Team

Every modern website and application benefits from caching content closer to users. Commercial CDNs like Cloudflare, Fastly, and AWS CloudFront dominate this space, but they come with trade-offs: your traffic flows through third-party infrastructure, pricing scales unpredictably, and you surrender control over cache behavior.

Building a self-hosted CDN or edge caching layer gives you full visibility into what gets cached, how long it stays, and where it’s stored. For organizations handling sensitive data, operating under strict compliance requirements, or simply wanting to reduce external dependencies, self-hosted caching infrastructure is a practical necessity.

Why Self-Host Your Caching Layer

There are several compelling reasons to run your own caching infrastructure instead of relying on commercial CDNs.

Data sovereignty and privacy — When you use a commercial CDN, every request passes through their servers. For healthcare, financial, or government applications, this can create compliance issues with regulations like GDPR, HIPAA, or SOC 2. Self-hosted caching keeps all traffic within your controlled infrastructure.

Cost predictability — Commercial CDN pricing is based on bandwidth, requests, and features. A popular site can see bills spike from $50 to $5,000 overnight during traffic surges. Self-hosted solutions run on hardware you already own, with costs limited to electricity and server capacity.

Fine-grained control — Commercial CDNs offer preset cache rules. With self-hosted tools, you control every aspect: cache keys, TTL logic, purge strategies, custom VCL configurations, and integration with your existing monitoring stack.

Reduced vendor lock-in — Migrating away from a CDN provider means reconfiguring DNS, updating cache rules, and revalidating certificates. A self-hosted stack is yours to keep, modify, and extend without vendor approval.

What Makes a Good Self-Hosted Caching Solution

Not all caching software is created equal. When evaluating options for a self-hosted CDN or reverse proxy cache, consider these criteria:

  • HTTP compliance — Full support for HTTP/1.1, HTTP/2, and HTTP/3 with correct cache header handling (Cache-Control, ETag, Vary, Last-Modified)
  • Cache invalidation — Support for PURGE, BAN, and soft-purge operations
  • SSL/TLS termination — Built-in HTTPS support with Let’s Encrypt integration
  • Load balancing — Ability to distribute requests across multiple backend servers
  • Varnish Configuration Language or equivalent — Scriptable cache logic for complex routing
  • Monitoring and metrics — Export of cache hit/miss ratios, bandwidthprometheusonse times to Prometheus, Grafana, or similar tools
  • Horizontal scaling — Support for running multiple cache nodes behind a DNS-based or anycast routing layer

Varnish Cache

Varnish Cache is the gold standard for HTTP acceleration. Originally designed to power Norwegian newspapers with millions of daily readers, it’s now used by Wikipedia, The New York Times, and countless high-traffic sites.

Key Features

  • Purpose-built as an HTTP accelerator — not a general-purpose proxy
  • VCL (Varnish Configuration Language) for flexible cache logic
  • Edge Side Includes (ESI) for assembling cached page fragments
  • Grace mode to serve stale content when backends are down
  • Built-in health checks fdockerkend servers

Docker Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: "3.8"

services:
  varnish:
    image: varnish:7.6
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./default.vcl:/etc/varnish/default.vcl:ro
      - varnish_cache:/var/lib/varnish
    environment:
      - VARNISH_SIZE=2G
    command: >
      -f /etc/varnish/default.vcl
      -s malloc,2G
      -p thread_pool_min=50
      -p thread_pool_max=1000
      -p http_resp_hdr_len=65536
    restart: unless-stopped

volumes:
  varnish_cache:

Core VCL Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
vcl 4.1;

backend default {
    .host = "origin-server";
    .port = "8080";
    .probe = {
        .url = "/health";
        .interval = 10s;
        .timeout = 2s;
        .window = 5;
        .threshold = 3;
    }
}

sub vcl_recv {
    # Remove tracking cookies from cache key
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|__utma?|__utmz|ga_client_id)=[^;]*", "");
        if (req.http.Cookie == "") {
            unset req.http.Cookie;
        }
    }

    # Normalize Accept-Encoding for better cache hit rates
    if (req.http.Accept-Encoding) {
        if (req.url ~ "\.(jpg|jpeg|png|gif|webp|ico|css|js)$") {
            # Don't compress static assets
            unset req.http.Accept-Encoding;
        } elsif (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
        } else {
            unset req.http.Accept-Encoding;
        }
    }

    # Cache only GET and HEAD requests
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }

    # Don't cache authenticated sessions
    if (req.http.Authorization) {
        return (pass);
    }

    return (hash);
}

sub vcl_backend_response {
    # Set default TTL for static assets
    if (bereq.url ~ "\.(jpg|jpeg|png|gif|webp|ico|css|js|woff2?)$") {
        set beresp.ttl = 30d;
        set beresp.grace = 30d;
        unset beresp.http.Set-Cookie;
    }

    # Set default TTL for HTML pages
    if (bereq.url ~ "\.html$") {
        set beresp.ttl = 1h;
        set beresp.grace = 2h;
    }

    # Remove server headers for security
    unset beresp.http.X-Varnish;
    unset beresp.http.Via;
    unset beresp.http.Server;

    return (deliver);
}

sub vcl_deliver {
    # Add cache hit/miss header for debugging
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
        set resp.http.X-Cache-Hits = obj.hits;
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

Purge and Ban Operations

1
2
3
4
5
6
7
8
# Purge a single URL
curl -X PURGE https://your-domain.com/page-to-clear

# Ban all cached content matching a pattern
varnishadm 'ban req.url ~ ^/api/v1/ && req.http.host == your-domain.com'

# View active bans
varnishadm ban.list

Performance Tuning

Varnish excels with in-memory caching. For a server with 16 GB RAM:

1
2
3
4
5
6
-p thread_pool_min=100
-p thread_pool_max=2000
-p thread_pool_timeout=300
-p http_resp_hdr_len=131072
-p workspace_backend=256k
-s malloc,8G

For disk-based caching on large content libraries:

1
2
-s file,/var/lib/varnish/storage.bin,50G
-p lru_interval=3600

Varnish does not handle TLS natively. You need a TLS terminator like Hitch, Nginx, or Caddy in front of it.

Apache Traffic Server

Apache Traffic Server (ATS) is a high-performance reverse proxy and caching server originally developed by Yahoo for their portal infrastructure. It handles hundreds of thousands of requests per second and powers CDNs serving billions of requests daily.

Key Features

  • Hierarchical caching with parent proxy support
  • Plugin architecture with over 50 official plugins
  • HTTP/2 and HTTP/3 support (experimental HTTP/3 in 10.x)
  • Traffic control for bandwidth management
  • Integrated logging and statistics

Docker Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: "3.8"

services:
  trafficserver:
    image: apache/trafficserver:10.0
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - ./records.yaml:/opt/trafficserver/etc/trafficserver/records.yaml:ro
      - ./remap.config:/opt/trafficserver/etc/trafficserver/remap.config:ro
      - ./storage.config:/opt/trafficserver/etc/trafficserver/storage.config:ro
      - ats_cache:/opt/trafficserver/var/trafficserver
    restart: unless-stopped

volumes:
  ats_cache:

Core Configuration

records.yaml — main server settings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
proxy.config.http.server_ports: "80 443:ssl"
proxy.config.reverse_proxy.enabled: 1
proxy.config.url_remap.remap_required: 1
proxy.config.cache.ram_cache.size: 4GB
proxy.config.cache.ram_cache.algorithm: 1
proxy.config.http.cache.http: 1
proxy.config.http.cache.ignore_client_no_cache: 1
proxy.config.http.cache.ignore_client_cc_max_age: 1
proxy.config.http.normalize_ae_gzip: 1
proxy.config.http.cache.max_open_read_retries: 5
proxy.config.diags.logfile.filename: "traffic.out"

remap.config — URL routing rules:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Reverse proxy mapping
map https://your-domain.com/ http://origin-server:8080/ \
  @plugin=cachekey.so \
  @plugin=header_rewrite.so @pparam=cache_headers.config

# API endpoints with shorter TTL
map https://api.your-domain.com/ http://api-backend:3000/ \
  @plugin=cachekey.so \
  @plugin=header_rewrite.so @pparam=api_cache.config

# Static assets with aggressive caching
map https://static.your-domain.com/ http://static-backend:8080/ \
  @plugin=cachekey.so \
  @plugin=header_rewrite.so @pparam=static_cache.config

cache_headers.config — cache behavior for main site:

1
2
3
cond %{READ_RESPONSE_HDR_HOOK}
add-header X-Cache-Lookup MISS
set-header X-Cache-Lookup HIT if-cached

Cache Monitoring

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Show cache statistics
traffic_ctl metric match proxy.process.cache

# Show HTTP transaction stats
traffic_ctl metric match proxy.process.http

# Reload configuration without restart
traffic_ctl config reload

# View active connections
traffic_ctl metric get proxy.process.http.current_client_connections

Apache Traffic Server handles TLS natively and includes SSL certificate management through its records configuration. Its hierarchical caching model allows you to build multi-tier cache architectures with parent-child relationships between nodes.

Squid Proxy

Squid is one of the oldest and most widely deployed caching proxy servers. While often associated with forward proxying, Squid excels as a reverse proxy cache and is particularly strong in environments requiring fine-grained access control and content filtering.

Key Features

  • Reverse proxy acceleration mode
  • ICP (Internet Cache Protocol) for cache peer communication
  • Hierarchical cache arrays with parent/sibling relationships
  • Extensive ACL system for request filtering
  • Support for SSL Bumping (with limitations)
  • Built-in delay pools for bandwidth throttling

Docker Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: "3.8"

services:
  squid:
    image: ubuntu/squid:latest
    ports:
      - "80:3128"
      - "443:3129"
    volumes:
      - ./squid.conf:/etc/squid/squid.conf:ro
      - ./ssl_cert.pem:/etc/squid/ssl_cert.pem:ro
      - squid_cache:/var/spool/squid
    environment:
      - TZ=UTC
    restart: unless-stopped

volumes:
  squid_cache:

Core Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Basic settings
http_port 3128
https_port 3129 cert=/etc/squid/ssl_cert.pem key=/etc/squid/ssl_key.pem

# Reverse proxy mode
http_port 80 accel defaultsite=your-domain.com
cache_peer origin-server parent 8080 0 no-query originserver name=origin

# Cache configuration
cache_dir ufs /var/spool/squid 10240 16 256
cache_mem 256 MB
maximum_object_size 100 MB
maximum_object_size_in_memory 512 KB

# Cache replacement policy
cache_replacement_policy lru
memory_replacement_policy lru

# Access control
acl localnet src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
acl SSL_ports port 443
acl Safe_ports port 80 8080

# Cache rules
cache allow all
cache deny PURGE

# Refresh patterns
refresh_pattern -i \.jpg$    10080 90% 43200 ignore-reload
refresh_pattern -i \.png$    10080 90% 43200 ignore-reload
refresh_pattern -i \.css$    10080 90% 43200 ignore-reload
refresh_pattern -i \.js$     10080 90% 43200 ignore-reload
refresh_pattern -i \.html$   60    50%  1440
refresh_pattern .            60    50%  1440

# Logging
access_log /var/log/squid/access.log squid
cache_log /var/log/squid/cache.log
cache_store_log /var/log/squid/store.log

# Purge support
acl purge method PURGE
http_access allow purge localhost
http_access deny purge
http_access allow localnet
http_access deny all

Cache Management

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Rotate logs
squid -k rotate

# Reconfigure without restart
squid -k reconfigure

# Graceful shutdown
squid -k shutdown

# Check cache directory health
squid -k check

# View cache statistics
squidclient -p 80 mgr:info
squidclient -p 80 mgr:5min

Squid’s strength lies in its maturity and extensive ACL system. It is well-suited for organizations that need content filtering alongside caching, such as educational institutions or corporate networks. However, its refresh pattern syntax is more complex than Varnish’s VCL, and it lacks a modern configuration language for advanced cache logic.

Nginx as Reverse Proxy Cache

Nginx is primarily a web server and reverse proxy, but its built-in caching module is powerful enough to serve as the caching layer for many self-hosted CDN deployments. Its simplicity and ubiquity make it an excellent choice for teams already running Nginx in their stack.

Key Features

  • Integrated caching with no additional software
  • Proxy cache purge module (requires compilation or third-party module)
  • Key-value store (Nginx Plus feature, not available in open source)
  • HTTP/2 and HTTP/3 native support
  • Lua scripting with OpenResty for advanced cache logic
  • Built-in load balancing with multiple algorithms

Docker Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: "3.8"

services:
  nginx-cache:
    image: nginx:1.27
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./ssl:/etc/nginx/ssl:ro
      - nginx_cache:/var/cache/nginx
      - ./certs:/etc/letsencrypt:ro
    restart: unless-stopped

volumes:
  nginx_cache:

Core Configuration

nginx.conf — main configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 4096;
    multi_accept on;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging format with cache status
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'cache_status=$upstream_cache_status rt=$request_time';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 256;
    gzip_types
        text/plain text/css application/json application/javascript
        text/xml application/xml application/xml+rss text/javascript
        image/svg+xml;

    # Proxy cache zone definitions
    proxy_cache_path /var/cache/nginx/static
        levels=1:2
        keys_zone=static_cache:50m
        max_size=10g
        inactive=30d
        use_temp_path=off;

    proxy_cache_path /var/cache/nginx/dynamic
        levels=1:2
        keys_zone=dynamic_cache:20m
        max_size=2g
        inactive=1h
        use_temp_path=off;

    proxy_cache_path /var/cache/nginx/api
        levels=1:2
        keys_zone=api_cache:10m
        max_size=500m
        inactive=5m
        use_temp_path=off;

    # Rate limiting zones
    limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    include /etc/nginx/conf.d/*.conf;
}

conf.d/default.conf — server block with caching:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
server {
    listen 80;
    listen [::]:80;
    server_name your-domain.com;

    # Redirect to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # Upstream backends
    upstream origin_backend {
        server origin-server:8080;
        server origin-server-2:8080 backup;
        keepalive 32;
    }

    upstream api_backend {
        server api-server:3000;
        least_conn;
        keepalive 16;
    }

    upstream static_backend {
        server static-server:8080;
        keepalive 32;
    }

    # Main site — dynamic content with short cache
    location / {
        limit_req zone=general burst=50 nodelay;

        proxy_pass http://origin_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_cache dynamic_cache;
        proxy_cache_valid 200 1h;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_lock on;
        proxy_cache_lock_timeout 5s;
        proxy_cache_background_update on;

        add_header X-Cache-Status $upstream_cache_status;
    }

    # Static assets — long cache
    location ~* \.(jpg|jpeg|png|gif|webp|ico|css|js|woff2?|svg|mp4|webm)$ {
        proxy_pass http://static_backend;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_cache static_cache;
        proxy_cache_valid 200 30d;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;

        expires 30d;
        add_header Cache-Control "public, immutable";
        add_header X-Cache-Status $upstream_cache_status;
    }

    # API endpoints — short cache with rate limiting
    location /api/ {
        limit_req zone=api burst=20 nodelay;

        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_cache api_cache;
        proxy_cache_valid 200 5m;
        proxy_cache_valid 404 30s;
        proxy_cache_methods GET HEAD;
        proxy_cache_bypass $http_cache_control;

        add_header X-Cache-Status $upstream_cache_status;
    }

    # Purge endpoint (restricted to localhost)
    location /purge/ {
        allow 127.0.0.1;
        allow ::1;
        deny all;

        proxy_cache_purge static_cache $scheme$proxy_host$request_uri;
    }

    # Health check endpoint
    location /health {
        access_log off;
        return 200 "OK";
        add_header Content-Type text/plain;
    }
}

Cache Purge Operations

The open-source Nginx requires a third-party module (nginx-cache-purge) for programmatic cache purging. With the module compiled in:

1
2
3
4
5
# Purge a specific URL
curl -X PURGE https://your-domain.com/purge/page/to-clear

# Using the proxy_cache_purge directive via internal endpoint
curl https://your-domain.com/purge/static/image.jpg

Cache Monitoring with Prometheus

Export Nginx cache metrics using the Nginx Prometheus Exporter:

1
2
3
4
5
6
  nginx-exporter:
    image: nginx/nginx-prometheus-exporter:1.3
    ports:
      - "9113:9113"
    command:
      - -nginx.scrape-uri=http://nginx-cache:8080/stub_status

Comparison: Which Tool Should You Choose?

FeatureVarnish CacheApache Traffic ServerSquid ProxyNginx Cache
Primary roleHTTP acceleratorReverse proxy cacheForward/reverse proxyWeb server with cache
Cache performanceExcellentExcellentGoodVery good
Configuration languageVCL (powerful)YAML + pluginsDirective-basedNginx directives
TLS/SSL supportNo (needs Hitch/Nginx)NativeNativeNative
HTTP/2Yes (via TLS terminator)YesLimitedYes
HTTP/3Via externalExperimental (10.x)NoYes (1.25+)
Cache invalidationPURGE + BANPlugin-basedPURGEModule required
ESI supportYesYesNoVia Lua/Subs filter
Load balancingBasic (director)AdvancedLimitedAdvanced
Monitoringvarnishstat + varnishlogtraffic_ctl metricssquidclient mgr:stub_status + exporter
Plugin ecosystemVCL + VMODs50+ official pluginsACL helpersLua + third-party modules
Learning curveSteep (VCL)ModerateModerateGentle
Best forHigh-performance sitesMulti-tier CDN setupsFiltering + cachingGeneral web infrastructure

Building a Multi-Node Self-Hosted CDN

A single cache server handles a surprising amount of traffic, but true CDN resilience comes from geographic distribution. Here is how to build a multi-node setup.

Architecture Overview

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
                    ┌─────────────┐
                    │   DNS-Based  │
                    │ Geo-Routing  │
                    └──────┬──────┘
          ┌────────────────┼────────────────┐
          ▼                ▼                ▼
   ┌───────────┐    ┌───────────┐    ┌───────────┐
   │ Varnish   │    │ Varnish   │    │ Varnish   │
   │ US-East   │    │ EU-West   │    │ AP-South  │
   └─────┬─────┘    └─────┬─────┘    └─────┬─────┘
         │                │                │
         └────────────────┼────────────────┘
                   ┌─────────────┐
                   │ Origin Server│
                   │ (Backend)   │
                   └─────────────┘

Geo-DNS Configuration with GeoDNS

Using bind or PowerDNS with geo-IP routing:

1
2
3
4
5
6
; PowerDNS geo backend example
$ORIGIN your-domain.com
@       IN  A       203.0.113.10    ; Default
us      IN  A       203.0.113.10    ; US-East node
eu      IN  A       198.51.100.20   ; EU-West node
ap      IN  A       192.0.2.30      ; AP-South node

Cache Coordination with ICP (Squid) or Parent Proxies (ATS)

For Apache Traffic Server parent-child hierarchy:

1
2
3
4
# parent.config
origin-server:8080 go_parent round_robin=strict
ats-eu-west:8080 go_parent round_robin=consistent_hash
ats-ap-south:8080 go_parent round_robin=consistent_hash

Monitoring Your Self-Hosted CDN

Regardless of which tool you choose, monitoring cache performance is essential. A typical Prometheus + Grafana setup covers the basics.

Key Metrics to Track

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Cache hit ratio (Varnish)
rate(varnish_main_cache_hit[5m]) / (rate(varnish_main_cache_hit[5m]) + rate(varnish_main_cache_miss[5m]))

# Backend response time
histogram_quantile(0.95, rate(varnish_backend_response_time_seconds_bucket[5m]))

# Cache utilization (Nginx)
nginx_cache_bytes_used / nginx_cache_max_bytes

# Active connections
nginx_connections_active

Grafana Dashboard Essentials

  • Cache hit ratio over time (target: 80-95% for static, 40-70% for dynamic)
  • Backend response latency (P50, P95, P99)
  • Bandwidth in/out per cache node
  • Cache eviction rate
  • Error rate by HTTP status code
  • Active connections and thread utilization

When to Use Each Solution

Choose Varnish Cache when you need maximum HTTP acceleration performance, fine-grained cache logic with VCL, ESI for page composition, or are running a high-traffic content site.

Choose Apache Traffic Server when you need hierarchical caching across multiple tiers, a rich plugin ecosystem, native TLS support with caching, or are building a CDN-like infrastructure.

Choose Squid Proxy when you need content filtering alongside caching, ICP-based cache mesh networks, extensive ACL-based access control, or are operating in an educational/corporate environment.

Choose Nginx Cache when you already run Nginx and want integrated caching, need HTTP/3 support out of the box, prefer simple configuration over advanced cache logic, or want a single binary for web serving and caching.

Getting Started Checklist

  1. Pick your caching tool based on the comparison above
  2. Deploy with Docker Compose on your edge server
  3. Configure your origin backend(s) in the proxy configuration
  4. Set cache TTLs per content type (static vs dynamic vs API)
  5. Enable cache status headers for debugging (X-Cache: HIT/MISS)
  6. Set up Prometheus metrics collection
  7. Configure cache purge endpoints and access controls
  8. Deploy TLS certificates (Let’s Encrypt with Certbot)
  9. Test cache behavior with curl -v and verify headers
  10. Set up Grafana dashboards for ongoing monitoring

A self-hosted caching layer is one of the highest-ROI infrastructure improvements you can make. It reduces origin server load, cuts bandwidth costs, improves page load times, and keeps your traffic under your own control. Start with a single node, validate your cache hit ratios, and expand to a multi-node setup as your traffic grows.

Frequently Asked Questions (FAQ)

Which one should I choose in 2026?

The best choice depends on your specific requirements:

  • For beginners: Start with the simplest option that covers your core use case
  • For production: Choose the solution with the most active community and documentation
  • For teams: Look for collaboration features and user management
  • For privacy: Prefer fully open-source, self-hosted options with no telemetry

Refer to the comparison table above for detailed feature breakdowns.

Can I migrate between these tools?

Most tools support data import/export. Always:

  1. Backup your current data
  2. Test the migration on a staging environment
  3. Check official migration guides in the documentation

Are there free versions available?

All tools in this guide offer free, open-source editions. Some also provide paid plans with additional features, priority support, or managed hosting.

How do I get started?

  1. Review the comparison table to identify your requirements
  2. Visit the official documentation (links provided above)
  3. Start with a Docker Compose setup for easy testing
  4. Join the community forums for troubleshooting
Advertise here