Episode 6 — Scaling Reliability Microservices Web3 / 6.8 — Production Hardening
6.8.c — DDoS Protection
In one sentence: A DDoS (Distributed Denial of Service) attack overwhelms your system from thousands of sources simultaneously — defending against it requires layered protection from the network edge all the way down to your application code.
Navigation: ← 6.8.b — CORS and Secure Headers · 6.8 Overview →
1. What Is a DDoS Attack?
A Distributed Denial of Service attack floods your infrastructure with so much traffic that legitimate users cannot access your service. Unlike a DoS (single source), DDoS comes from thousands or millions of compromised devices (a botnet).
Normal Traffic:
User A ──→ ┐
User B ──→ ├──→ Server ──→ ✅ All served
User C ──→ ┘
DDoS Attack:
Bot 1 ──→ ┐
Bot 2 ──→ │
Bot 3 ──→ │
... ├──→ Server ──→ ✗ Overwhelmed
Bot 9998 ──→ │ Legitimate users
Bot 9999 ──→ │ cannot connect
Bot 10000──→ ┘
User A ──→ ✗ Connection refused / timeout
2. DDoS Attack Types
2.1 Volumetric Attacks (Layer 3/4)
Flood the network with massive amounts of data. Goal: saturate bandwidth.
Examples:
- UDP Flood: Send massive UDP packets to random ports
- ICMP Flood: Overwhelm with ping packets
- DNS Amplification: Tiny request → huge response reflected at target
Scale: 100 Gbps to 3+ Tbps (terabits per second)
Defense: Can only be stopped at the network edge (CDN, ISP)
2.2 Protocol Attacks (Layer 3/4)
Exploit protocol weaknesses to exhaust server resources (connection tables, firewalls).
Examples:
- SYN Flood: Send thousands of TCP SYN packets, never complete handshake
- Ping of Death: Malformed oversized ICMP packets
- Smurf Attack: Broadcast ICMP to amplify traffic
Target: Connection state tables, load balancers, firewalls
SYN Flood:
Attacker Server
│ SYN ──────────────────→ │ Connection table: [ HALF_OPEN ]
│ SYN ──────────────────→ │ Connection table: [ HALF_OPEN, HALF_OPEN ]
│ SYN ──────────────────→ │ Connection table: [ HALF_OPEN, HALF_OPEN, ... ]
│ ... │
│ SYN ──────────────────→ │ Connection table: FULL → new connections rejected
│ (never sends ACK) │
│ Legitimate user SYN → REJECTED (no room)
2.3 Application Layer Attacks (Layer 7)
Target your application logic with seemingly legitimate HTTP requests.
Examples:
- HTTP Flood: Thousands of GET /api/search?q=... requests
- Slowloris: Open connections, send headers very slowly
- POST Flood: Submit large forms/JSON payloads repeatedly
- API Abuse: Hit expensive endpoints (search, reports, AI)
Why they're dangerous:
- Look like legitimate traffic (hard to distinguish)
- Small bandwidth but high server CPU/DB cost
- WAF and CDN may pass them through
Slowloris Attack:
Attacker opens 1000 connections:
Connection 1: "GET / HTTP/1.1\r\nHost: example.com\r\nX-a: b\r\n"
... waits 10 seconds ...
"X-c: d\r\n" ← sends another header, keeps alive
... waits 10 seconds ...
"X-e: f\r\n" ← never sends final \r\n\r\n
Connection 2: same pattern
...
Connection 1000: same pattern
Result: All server connections occupied → legitimate users get "503"
3. DDoS Protection Layers
┌──────────────────────────────────────────────────────────────────────┐
│ DEFENSE IN DEPTH │
│ │
│ Layer 1: DNS / CDN Edge │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ CloudFront / CloudFlare │ │
│ │ - Absorbs volumetric attacks (Tbps capacity) │ │
│ │ - Geographic distribution │ │
│ │ - TLS termination │ │
│ └────────────────────────┬─────────────────────────────────┘ │
│ ▼ │
│ Layer 2: AWS Shield │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Shield Standard (free) / Shield Advanced (paid) │ │
│ │ - Automatic L3/L4 attack mitigation │ │
│ │ - DDoS Response Team (Advanced) │ │
│ │ - Cost protection (Advanced) │ │
│ └────────────────────────┬─────────────────────────────────┘ │
│ ▼ │
│ Layer 3: WAF (Web Application Firewall) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ AWS WAF / CloudFlare WAF │ │
│ │ - SQL injection rules │ │
│ │ - XSS pattern blocking │ │
│ │ - Rate-based rules (L7) │ │
│ │ - Geo-blocking │ │
│ │ - Bot detection │ │
│ └────────────────────────┬─────────────────────────────────┘ │
│ ▼ │
│ Layer 4: Application Rate Limiting │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ express-rate-limit / Redis-based limiting │ │
│ │ - Per-user / per-IP / per-key quotas │ │
│ │ - Endpoint-specific limits │ │
│ └────────────────────────┬─────────────────────────────────┘ │
│ ▼ │
│ Layer 5: Application Hardening │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Input validation, connection limits, request size caps │ │
│ │ Timeout enforcement, queue-based processing │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
4. AWS Shield
Shield Standard (Free)
Automatically included with all AWS services. Protects against most common L3/L4 DDoS attacks.
- Automatic detection — always-on monitoring
- Inline mitigation — absorbs SYN floods, UDP floods, reflection attacks
- No configuration needed — works on CloudFront, Route 53, ALB, ELB
- No extra cost
Shield Advanced ($3,000/month)
For mission-critical applications. Includes everything in Standard plus:
| Feature | What It Does |
|---|---|
| DDoS Response Team (DRT) | 24/7 team that helps during active attacks |
| Advanced detection | Tailored to your traffic patterns |
| Cost protection | AWS credits for DDoS-related scaling costs |
| Real-time metrics | CloudWatch dashboards for attack visibility |
| WAF integration | Auto-creates WAF rules during attacks |
| Health-based detection | Uses Route 53 health checks for faster response |
When to use Shield Advanced:
- Revenue-critical applications (e-commerce, banking)
- Applications that cannot tolerate ANY downtime
- When DDoS-related auto-scaling would cost more than $3K/month
- Regulatory requirements for DDoS protection
5. CloudFront as DDoS Protection
CloudFront is not just a CDN — it is your first line of DDoS defense.
How CloudFront absorbs attacks:
Attack traffic → 600+ Edge Locations worldwide
│
▼
┌────────────────┐
│ CloudFront │
│ Edge Location │
│ │
│ - Massive │
│ bandwidth │
│ - Shield │
│ Standard │
│ - Can cache │
│ responses │
│ - WAF at edge │
└───────┬────────┘
│ Only legitimate
│ traffic passes
▼
┌────────────────┐
│ Your Origin │
│ (ALB/EC2/ECS) │
└────────────────┘
Key CloudFront DDoS Features
// CloudFront distribution config (conceptual — AWS CDK)
const distribution = new cloudfront.Distribution(this, 'Dist', {
defaultBehavior: {
origin: new origins.HttpOrigin('api.example.com'),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
// Attach WAF
webAclId: wafWebAcl.attrArn,
},
// Geo-restriction
geoRestriction: cloudfront.GeoRestriction.allowlist('US', 'CA', 'GB', 'DE'),
// Custom error responses (don't reveal origin errors)
errorResponses: [
{
httpStatus: 503,
responseHttpStatus: 503,
responsePagePath: '/error.html',
ttl: Duration.seconds(10),
},
],
});
6. WAF (Web Application Firewall)
AWS WAF inspects HTTP/HTTPS requests and decides whether to allow, block, or count them based on rules.
WAF Rule Types
| Rule Type | What It Blocks | Example |
|---|---|---|
| IP-based | Known bad IPs, IP reputation lists | Block IPs from threat intelligence feeds |
| Rate-based | Too many requests from one IP | Block IP if > 2000 req / 5 min |
| String match | Patterns in request (SQLi, XSS) | Block if query string contains UNION SELECT |
| Regex | Complex pattern matching | Block unusual user-agent patterns |
| Geo-match | Requests from specific countries | Block all traffic from countries you don't serve |
| Size constraint | Oversized requests | Block request body > 8KB |
| Managed rules | AWS/partner pre-built rule sets | OWASP Top 10, Bot Control, Known Bad Inputs |
WAF Configuration Example
// AWS WAF Web ACL (conceptual — AWS CDK)
const webAcl = new wafv2.CfnWebACL(this, 'WebACL', {
defaultAction: { allow: {} },
scope: 'CLOUDFRONT',
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'productionWAF',
},
rules: [
// Rule 1: Rate limiting
{
name: 'RateLimit',
priority: 1,
action: { block: {} },
statement: {
rateBasedStatement: {
limit: 2000, // 2000 requests per 5 minutes
aggregateKeyType: 'IP',
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'RateLimitRule',
},
},
// Rule 2: AWS Managed Rules — SQL injection
{
name: 'SQLInjection',
priority: 2,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesSQLiRuleSet',
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'SQLiRule',
},
},
// Rule 3: AWS Managed Rules — Known bad inputs
{
name: 'KnownBadInputs',
priority: 3,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesKnownBadInputsRuleSet',
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'KnownBadInputsRule',
},
},
// Rule 4: Geo-blocking
{
name: 'GeoBlock',
priority: 4,
action: { block: {} },
statement: {
notStatement: {
statement: {
geoMatchStatement: {
countryCodes: ['US', 'CA', 'GB', 'DE', 'FR', 'AU'],
},
},
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'GeoBlockRule',
},
},
],
});
7. Application-Level DDoS Mitigation
Even with CDN, Shield, and WAF, your application code must be resilient.
7.1 Connection Limiting
import express from 'express';
const app = express();
// Track active connections
let activeConnections = 0;
const MAX_CONNECTIONS = 1000;
const server = app.listen(3000);
server.on('connection', (socket) => {
activeConnections++;
if (activeConnections > MAX_CONNECTIONS) {
socket.destroy(); // Refuse new connections
return;
}
socket.on('close', () => {
activeConnections--;
});
});
7.2 Request Size Limits
// Limit JSON body size
app.use(express.json({ limit: '1mb' }));
// Limit URL-encoded body size
app.use(express.urlencoded({ limit: '1mb', extended: true }));
// Limit file uploads
import multer from 'multer';
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024, // 5MB max
files: 5, // Max 5 files
fields: 20, // Max 20 non-file fields
},
});
7.3 Request Timeout Enforcement
// Timeout middleware — kill slow requests
function requestTimeout(limitMs) {
return (req, res, next) => {
const timer = setTimeout(() => {
if (!res.headersSent) {
res.status(408).json({ error: 'Request timeout' });
}
}, limitMs);
res.on('finish', () => clearTimeout(timer));
res.on('close', () => clearTimeout(timer));
next();
};
}
// 30-second timeout for normal requests
app.use(requestTimeout(30000));
// 5-second timeout for health checks
app.get('/health', requestTimeout(5000), (req, res) => {
res.json({ status: 'ok' });
});
7.4 Slowloris Protection
// Set server-level timeouts to prevent Slowloris
const server = app.listen(3000);
server.headersTimeout = 20000; // 20s to receive full headers
server.requestTimeout = 30000; // 30s to receive full request
server.keepAliveTimeout = 5000; // Close idle keep-alive connections
server.timeout = 60000; // 60s total request timeout
7.5 Queue-Based Processing for Expensive Endpoints
import Bull from 'bull';
const reportQueue = new Bull('report-generation', {
redis: process.env.REDIS_URL,
limiter: {
max: 10, // Process at most 10 jobs
duration: 60000, // per minute
},
});
// Instead of processing inline, queue the work
app.post('/api/reports/generate', async (req, res) => {
const job = await reportQueue.add({
userId: req.user.id,
params: req.body,
});
res.status(202).json({
message: 'Report queued',
jobId: job.id,
statusUrl: `/api/reports/status/${job.id}`,
});
});
// Client polls for result
app.get('/api/reports/status/:jobId', async (req, res) => {
const job = await reportQueue.getJob(req.params.jobId);
if (!job) return res.status(404).json({ error: 'Job not found' });
const state = await job.getState();
res.json({
state,
result: state === 'completed' ? job.returnvalue : null,
});
});
8. Bot Detection Basics
// Simple bot detection heuristics
function botDetection(req, res, next) {
const ua = req.headers['user-agent'] || '';
// Block empty user-agents
if (!ua) {
return res.status(403).json({ error: 'Forbidden' });
}
// Block known bot patterns (basic)
const botPatterns = [
/python-requests/i,
/curl\//i,
/wget\//i,
/scrapy/i,
/httpclient/i,
];
// Only block on sensitive endpoints — don't block legitimate tools on public APIs
if (req.path.startsWith('/api/auth/')) {
for (const pattern of botPatterns) {
if (pattern.test(ua)) {
return res.status(403).json({ error: 'Automated access not allowed' });
}
}
}
next();
}
// For production, use AWS WAF Bot Control or CloudFlare Bot Management
// They use ML-based fingerprinting, not just user-agent strings
9. Geo-Blocking
If you only serve certain regions, block traffic from everywhere else:
import geoip from 'geoip-lite';
const ALLOWED_COUNTRIES = new Set(['US', 'CA', 'GB', 'DE', 'FR', 'AU', 'IN']);
function geoBlock(req, res, next) {
const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim()
|| req.socket.remoteAddress;
const geo = geoip.lookup(ip);
if (geo && !ALLOWED_COUNTRIES.has(geo.country)) {
return res.status(403).json({
error: 'Service not available in your region',
});
}
next();
}
app.use(geoBlock);
Better approach: Do geo-blocking at WAF/CloudFront level — never lets the traffic reach your servers.
10. Complete Multi-Layer Defense Configuration
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';
const app = express();
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
// --- Layer 1: Trust proxy (behind CloudFront/ALB) ---
app.set('trust proxy', 1);
// --- Layer 2: HTTPS enforcement ---
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(301, `https://${req.hostname}${req.url}`);
}
next();
});
}
// --- Layer 3: Secure headers ---
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));
// --- Layer 4: CORS ---
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
credentials: true,
}));
// --- Layer 5: Request size limits ---
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ limit: '1mb', extended: true }));
// --- Layer 6: Global rate limit ---
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 500,
standardHeaders: true,
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args) => redis.sendCommand(args),
prefix: 'rl:global:',
}),
}));
// --- Layer 7: Strict auth rate limit ---
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true,
store: new RedisStore({
sendCommand: (...args) => redis.sendCommand(args),
prefix: 'rl:auth:',
}),
});
app.post('/api/auth/login', authLimiter);
app.post('/api/auth/register', authLimiter);
// --- Layer 8: Request timeout ---
const server = app.listen(3000);
server.headersTimeout = 20000;
server.requestTimeout = 30000;
server.keepAliveTimeout = 5000;
server.timeout = 60000;
// --- Layer 9: Connection tracking ---
let connections = 0;
server.on('connection', (socket) => {
connections++;
if (connections > 2000) { socket.destroy(); return; }
socket.on('close', () => connections--);
});
console.log('Multi-layer hardened server running on :3000');
11. Key Takeaways
- DDoS attacks come in three types — volumetric (bandwidth), protocol (connections), and application layer (logic).
- Application-layer attacks are the hardest to stop — they look like legitimate traffic.
- CloudFront is your first defense — it absorbs volumetric attacks with massive edge capacity.
- AWS Shield Standard is free — it handles most L3/L4 attacks automatically.
- WAF rules block malicious patterns — SQL injection, XSS, rate-based, and geo-blocking.
- Application hardening is the last line — timeouts, connection limits, request size caps, queue-based processing.
- Defense in depth — no single layer is enough. Every layer catches what the previous one missed.
Explain-It Challenge
- Your API is receiving 50,000 legitimate-looking GET requests per second from 10,000 different IPs. CloudFront passes them through. How do you mitigate this application-layer attack?
- Explain why Slowloris is effective against Node.js/Express and how to prevent it.
- A client says "we pay for AWS Shield — we don't need rate limiting." Why are they wrong?
Navigation: ← 6.8.b — CORS and Secure Headers · 6.8 Overview →