Building a Real-Time DDoS Detection Engine in Go: A Practical Guide

Every production web application faces the same threat: abnormal traffic spikes that can bring your service to its knees. While enterprise-grade DDoS protection exists, understanding how detection works under the hood makes you a better engineer—and Go's concurrency model makes it surprisingly approachable.

In this tutorial, we'll build a lightweight anomaly detection engine that watches HTTP traffic in real-time and flags suspicious patterns. You'll learn practical techniques for rate limiting, statistical anomaly detection, and concurrent data processing.

Understanding DDoS Detection Fundamentals

DDoS detection isn't about catching every malicious request—it's about identifying patterns that deviate from normal behavior. Three key signals matter:

Request rate anomalies: Sudden spikes in requests per second from a single IP or globally. A legitimate traffic surge happens gradually; attacks spike instantly.

Geographic clustering: 10,000 requests from a single /24 subnet in 60 seconds isn't organic traffic—it's likely a botnet.

Behavioral signatures: Repeated 404s, identical User-Agent strings across IPs, or requests with no referrer headers all indicate automated attacks rather than human browsing.

Our Go implementation will focus on the first two signals using sliding window counters and threshold-based detection.

Building the Traffic Monitor

Start with a data structure to track requests efficiently. Go's sync.Map provides thread-safe access without explicit locking:

package main

import (
    "sync"
    "time"
)

type TrafficMonitor struct {
    ipRequests   sync.Map // map[string]*RequestCounter
    globalCount  *RequestCounter
    threshold    int
    window       time.Duration
}

type RequestCounter struct {
    mu        sync.Mutex
    timestamps []time.Time
}

func NewTrafficMonitor(threshold int, window time.Duration) *TrafficMonitor {
    return &TrafficMonitor{
        globalCount: &RequestCounter{},
        threshold:   threshold,
        window:      window,
    }
}

The RequestCounter maintains a slice of timestamps for each IP. When checking for anomalies, we'll filter out timestamps older than our detection window (typically 60 seconds).

Implementing Sliding Window Detection

The core detection logic uses a sliding window: count requests within the last N seconds and trigger alerts when thresholds break:

func (tm *TrafficMonitor) RecordRequest(ip string) bool {
    now := time.Now()
    
    // Get or create counter for this IP
    val, _ := tm.ipRequests.LoadOrStore(ip, &RequestCounter{})
    counter := val.(*RequestCounter)
    
    counter.mu.Lock()
    defer counter.mu.Unlock()
    
    // Remove stale timestamps outside our window
    cutoff := now.Add(-tm.window)
    counter.timestamps = filterTimestamps(counter.timestamps, cutoff)
    
    // Add current request
    counter.timestamps = append(counter.timestamps, now)
    
    // Check if threshold exceeded
    if len(counter.timestamps) > tm.threshold {
        return true // Anomaly detected
    }
    
    return false
}

func filterTimestamps(timestamps []time.Time, cutoff time.Time) []time.Time {
    result := timestamps[:0]
    for _, t := range timestamps {
        if t.After(cutoff) {
            result = append(result, t)
        }
    }
    return result
}

This approach is memory-efficient because we automatically prune old data. For production systems handling millions of requests, consider using a ring buffer or switching to Redis with TTL-based expiry.

Integrating with HTTP Middleware

Wrap your existing HTTP handlers with detection middleware:

func (tm *TrafficMonitor) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := extractIP(r)
        
        if tm.RecordRequest(ip) {
            // Log the anomaly
            log.Printf("ALERT: Potential DDoS from %s", ip)
            
            // Optional: Rate limit or block
            http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}

func extractIP(r *http.Request) string {
    // Check X-Forwarded-For header first (if behind proxy)
    if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
        return strings.Split(xff, ",")[0]
    }
    return strings.Split(r.RemoteAddr, ":")[0]
}

Statistical Enhancement: Z-Score Detection

Fixed thresholds work but can produce false positives during legitimate traffic spikes (product launches, viral posts). Add statistical anomaly detection using z-scores:

func (tm *TrafficMonitor) CalculateZScore(currentRate float64) float64 {
    // Maintain rolling average and standard deviation
    // If z-score > 3.0, current rate is 3 standard deviations above mean
    mean := tm.calculateMean()
    stdDev := tm.calculateStdDev(mean)
    
    if stdDev == 0 {
        return 0
    }
    
    return (currentRate - mean) / stdDev
}

When z-score > 3, you're seeing a statistically significant deviation—likely an attack rather than normal variance.

Production Considerations

This implementation teaches core concepts, but production systems need additional layers:

Distributed detection: In multi-server deployments, aggregate metrics in Redis or a time-series database like Prometheus. Single-server detection misses distributed attacks.

False positive handling: Whitelist known IPs (health checkers, monitoring tools, APIs). Implement progressive rate limiting rather than hard blocks.

Memory bounds: Set maximum IP entries tracked simultaneously. Use LRU eviction to prevent memory exhaustion from IP rotation attacks.

Key Takeaways

Building a DDoS detector demystifies traffic analysis and teaches valuable Go patterns: concurrent map access with sync.Map, time-window algorithms, and HTTP middleware composition. While you'll likely use Cloudflare or AWS Shield in production, understanding detection mechanics helps you tune those tools intelligently and build complementary application-layer protections.

The complete working example demonstrates that effective security monitoring doesn't require complex machine learning—solid fundamentals, Go's concurrency primitives, and statistical thinking get you surprisingly far.

Start small: implement basic rate limiting, monitor your metrics, then iterate with statistical enhancements as you learn your traffic patterns. The best DDoS protection is the one you actually understand.