π§© Inside My Global CDN Infrastructure
Designing and deploying a custom CDN has given me complete ownership of how files are managed, cached, and served globally. In this article, I break down the exact infrastructure powering my CDN: from FastAPI origin APIs to 4 edge nodes synchronized with rsync
and served over NGINX, all tied together using GeoDNS routing.
π§± Architecture Overview
My CDN is split into two main components:
- Origin Node (
origin.juane-cdn.com
) - Edge Nodes (
eu1
,sa1
,dubai1
,us1
)
The origin node handles all writes (uploads, deletions, folder management) and exposes a REST API powered by FastAPI. The edge nodes are read-only replicas, updated regularly via rsync
from the origin.
π Origin Node
- API Framework: FastAPI
- Static server: NGINX
- Storage path:
/var/www/cdn-content/
- Security: hidden system files (
index.html
,403.html
, etc.) are protected from listing and deletion - Upload method:
POST /upload
with optionalpath
query - Access control: All files are served directly by NGINX with
try_files
and proper error handling - Error handling: NGINX serves
403.html
,404.html
, andindex.html
from the static directory
FastAPI Implementation
from fastapi import FastAPI, UploadFile, File, HTTPException
from pathlib import Path
import shutil
import os
app = FastAPI()
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...),
path: str = None
):
try:
# Validate file size (max 100MB)
if file.size > 100 * 1024 * 1024:
raise HTTPException(status_code=400, detail="File too large")
# Create directory if it doesn't exist
upload_dir = Path("/var/www/cdn-content") / (path or "")
upload_dir.mkdir(parents=True, exist_ok=True)
# Save file
file_path = upload_dir / file.filename
with file_path.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"filename": file.filename, "path": str(file_path)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
NGINX Configuration
server {
listen 80;
server_name origin.juane-cdn.com;
root /var/www/cdn-content;
location / {
try_files $uri $uri/ /index.html =404;
add_header Cache-Control "public, max-age=31536000";
}
location /api/ {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Protect system files
location ~ /(index|403|404)\.html$ {
internal;
}
}
π Edge Nodes (x4)
The edge nodes are geographically distributed for minimal latency:
| Location | Hostname | Region | Avg Latency | |----------------|------------------------|---------------|-------------| | Europe | eu1.juane-cdn.com | Frankfurt, DE | 15ms | | Saudi Arabia | sa1.juane-cdn.com | Riyadh | 25ms | | Dubai | dubai1.juane-cdn.com | UAE | 20ms | | United States | us1.juane-cdn.com | Virginia, US | 30ms |
Each edge node:
- Mirrors the origin's file structure via
rsync
- Runs NGINX with a long-lived
Cache-Control
header - Responds instantly to the closest users via GeoDNS
- Implements rate limiting and DDoS protection
Performance Metrics
- Cache Hit Ratio: 98.5%
- Average Response Time: 25ms
- Throughput: 10,000 requests/second per node
- Storage Capacity: 1TB per node
- Bandwidth: 1Gbps per node
π Synchronization via rsync
All edge nodes sync from the origin every 60 seconds:
rsync -az --delete [email protected]:/var/www/cdn-content/ /var/www/cdn-content/
This ensures low-latency updates with automatic deletion of stale files.
Sync Performance
- Average Sync Time: 2-3 seconds
- Bandwidth Usage: ~100MB/day
- Failed Syncs: < 0.1%
π GeoDNS Routing
The domain https://juane-cdn.com
is powered by GeoDNS, provided by Route 53. This means:
- Visitors are routed to the closest edge node based on their IP
- No need for client-side redirection
- Redundancy and fallback logic can be configured
Example policies:
- Europe traffic β
eu1.juane-cdn.com
- Middle East β
sa1
ordubai1
- North America β
us1
GeoDNS Configuration
# Route 53 Configuration
- Name: juane-cdn.com
Type: A
TTL: 300
Region: eu-west-1
Value: eu1.juane-cdn.com
- Name: juane-cdn.com
Type: A
TTL: 300
Region: me-south-1
Value: sa1.juane-cdn.com
π CDN File Access & Security
All files are served via NGINX using try_files
with restricted fallback. System-level files like 404.html
, 403.html
, index.html
, and favicon.ico
are:
- Excluded from API listings
- Cannot be deleted via API
- Served only when matched by status codes
Security Measures
-
Rate Limiting
- 1000 requests/minute per IP
- 100MB upload limit per file
- 1000 files per directory
-
DDoS Protection
- Cloudflare Enterprise
- Custom WAF rules
- IP reputation filtering
-
File Validation
- MIME type checking
- File size limits
- Malware scanning
π° Cost Analysis
Monthly costs breakdown:
| Component | Cost (USD) | |-------------------|------------| | Origin Server | $37 | | Edge Nodes (x4) | $48 | | Bandwidth | $50 | | GeoDNS | $5 | | Total | $140 |
Compared to commercial CDNs:
- Cloudflare: $200/month
- Akamai: $1000/month
- AWS CloudFront: $500/month
π Monitoring & Metrics
I'm currently integrating per-region request logging and sync status to build a metrics dashboard. This will include:
- Sync health
- Cache hit ratios per edge
- Geographic heatmaps
- Bandwidth usage
- Error rates
- Response times
Monitoring Stack
- Prometheus for metrics collection
- Grafana for visualization
- AlertManager for notifications
- ELK Stack for logs
πΊοΈ Global Distribution Map
π‘ Final Thoughts
Most developers default to third-party CDNs, but building my own stack allowed me to:
- Understand global delivery in depth
- Optimize for exactly what I need
- Control cost, caching, and consistency
- Implement custom security measures
- Scale based on actual usage
If you're managing large volumes of media or need strict control, a custom CDN is absolutely viable β and powerful. The key is to start small and scale based on your needs.