Implement Caddy 2 rate limiting and DDoS protection with advanced security rules

Intermediate 45 min Apr 26, 2026 80 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure Caddy 2 web server with comprehensive rate limiting, request throttling, and DDoS protection using built-in security modules and advanced filtering rules.

Prerequisites

  • Root or sudo access
  • Domain name pointed to your server
  • Basic understanding of web server configuration

What this solves

Caddy 2 provides built-in rate limiting and DDoS protection capabilities that help protect your web applications from malicious traffic and resource exhaustion attacks. This tutorial shows you how to implement comprehensive security rules including request rate limiting, connection throttling, IP-based filtering, and automated threat response to keep your services available under attack conditions.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest security updates.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Caddy 2 with security modules

Install Caddy 2 from the official repository to get the latest version with all security features enabled.

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
sudo dnf install -y yum-utils
sudo yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/caddy/caddy/repo/epel-8/caddy-caddy-epel-8.repo
sudo dnf install -y caddy

Create directory structure

Set up the necessary directories for Caddy configuration and security rules with proper permissions.

sudo mkdir -p /etc/caddy/security
sudo mkdir -p /var/log/caddy
sudo mkdir -p /var/www/html
sudo chown -R caddy:caddy /var/log/caddy
sudo chmod 755 /etc/caddy /etc/caddy/security

Configure basic rate limiting

Create a Caddyfile with fundamental rate limiting rules that protect against basic flooding attacks.

{
    # Global rate limiting options
    order rate_limit before basicauth
    servers {
        max_header_bytes 16KB
    }
}

example.com {
    # Enable access logging
    log {
        output file /var/log/caddy/access.log
        format single_field common_log
    }
    
    # Basic rate limiting - 100 requests per minute per IP
    rate_limit {
        zone static_ip {
            key {remote_host}
            events 100
            window 1m
        }
        
        # More restrictive for API endpoints
        zone api_ip {
            key {remote_host}
            events 20
            window 1m
        }
    }
    
    # Apply rate limits to specific paths
    @api path /api/*
    rate_limit @api api_ip
    
    # Default rate limit for all other requests
    rate_limit static_ip
    
    # Root directory
    root * /var/www/html
    file_server
}

Add advanced DDoS protection rules

Enhance the configuration with sophisticated protection against various types of DDoS attacks.

{
    order rate_limit before basicauth
    order request_body before rate_limit
    
    servers {
        max_header_bytes 16KB
        read_timeout 30s
        read_header_timeout 10s
        write_timeout 60s
        idle_timeout 120s
    }
}

example.com {
    log {
        output file /var/log/caddy/access.log
        format single_field common_log
    }
    
    # Request body size limits to prevent large payload attacks
    request_body {
        max_size 10MB
    }
    
    # Multiple rate limiting zones for different threat levels
    rate_limit {
        # Standard web traffic - 200 req/min per IP
        zone web_traffic {
            key {remote_host}
            events 200
            window 1m
        }
        
        # API endpoints - 50 req/min per IP
        zone api_strict {
            key {remote_host}
            events 50
            window 1m
        }
        
        # Authentication endpoints - 10 req/min per IP
        zone auth_endpoints {
            key {remote_host}
            events 10
            window 1m
        }
        
        # Search/query endpoints - 30 req/min per IP
        zone search_endpoints {
            key {remote_host}
            events 30
            window 1m
        }
        
        # File upload endpoints - 5 req/min per IP
        zone upload_endpoints {
            key {remote_host}
            events 5
            window 1m
        }
    }
    
    # Define request matchers for different endpoint types
    @auth path /login /register /auth/ /oauth/
    @api path /api/*
    @search path /search /query
    @upload path /upload /files/
    @static path .css .js .png .jpg .gif .ico .woff *.ttf
    
    # Apply appropriate rate limits
    rate_limit @auth auth_endpoints
    rate_limit @api api_strict
    rate_limit @search search_endpoints
    rate_limit @upload upload_endpoints
    
    # Lenient rate limiting for static assets
    @static_with_rate {
        path .css .js .png .jpg .gif .ico .woff *.ttf
    }
    rate_limit @static_with_rate {
        zone static_assets {
            key {remote_host}
            events 500
            window 1m
        }
    }
    
    # Default rate limit for other requests
    rate_limit web_traffic
    
    root * /var/www/html
    file_server
}

Implement IP blocking and security headers

Add IP-based access control and security headers to strengthen protection against attacks.

# Blocked IP addresses - one per line

192.168.1.100

10.0.0.50

{
    order rate_limit before basicauth
    order request_body before rate_limit
    
    servers {
        max_header_bytes 16KB
        read_timeout 30s
        read_header_timeout 10s
        write_timeout 60s
        idle_timeout 120s
    }
}

example.com {
    log {
        output file /var/log/caddy/access.log
        format single_field common_log
    }
    
    # Import blocked IPs (create matcher for blocked IPs)
    @blocked_ips {
        remote_ip 192.0.2.0/24 198.51.100.0/24
    }
    
    # Immediately respond with 403 for blocked IPs
    respond @blocked_ips "Access Denied" 403
    
    # Block requests with suspicious User-Agent strings
    @malicious_ua {
        header User-Agent bot crawler scanner nikto sqlmap
    }
    respond @malicious_ua "Forbidden" 403
    
    # Block requests with no User-Agent
    @no_ua {
        not header User-Agent *
    }
    respond @no_ua "Bad Request" 400
    
    # Security headers for all responses
    header {
        # Prevent clickjacking
        X-Frame-Options DENY
        
        # Prevent MIME type sniffing
        X-Content-Type-Options nosniff
        
        # Enable XSS protection
        X-XSS-Protection "1; mode=block"
        
        # Strict transport security
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        
        # Content security policy
        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
        
        # Referrer policy
        Referrer-Policy "strict-origin-when-cross-origin"
        
        # Remove server information
        -Server
        -X-Powered-By
    }
    
    # Request body size limits
    request_body {
        max_size 10MB
    }
    
    # Enhanced rate limiting with multiple zones
    rate_limit {
        zone web_traffic {
            key {remote_host}
            events 200
            window 1m
        }
        
        zone api_strict {
            key {remote_host}
            events 50
            window 1m
        }
        
        zone auth_endpoints {
            key {remote_host}
            events 10
            window 1m
        }
        
        zone search_endpoints {
            key {remote_host}
            events 30
            window 1m
        }
        
        zone upload_endpoints {
            key {remote_host}
            events 5
            window 1m
        }
        
        # Burst protection - very restrictive short-term limit
        zone burst_protection {
            key {remote_host}
            events 10
            window 10s
        }
    }
    
    # Apply burst protection to all requests first
    rate_limit burst_protection
    
    # Path-specific rate limiting
    @auth path /login /register /auth/ /oauth/
    @api path /api/*
    @search path /search /query
    @upload path /upload /files/
    
    rate_limit @auth auth_endpoints
    rate_limit @api api_strict
    rate_limit @search search_endpoints
    rate_limit @upload upload_endpoints
    rate_limit web_traffic
    
    root * /var/www/html
    file_server
}

Set up connection limits and timeouts

Configure connection-level protections to prevent resource exhaustion attacks.

{
    order rate_limit before basicauth
    order request_body before rate_limit
    
    # Global server settings with connection limits
    servers {
        max_header_bytes 16KB
        read_timeout 30s
        read_header_timeout 10s
        write_timeout 60s
        idle_timeout 120s
        
        # Connection limits per IP
        listener_wrappers {
            http_redirect
            tls
        }
        
        # Protocol settings
        protocols h1 h2
    }
    
    # Error handling
    handle_errors {
        @404 expression {http.error.status_code} == 404
        @429 expression {http.error.status_code} == 429
        @500 expression {http.error.status_code} >= 500
        
        handle @429 {
            respond "Rate limit exceeded. Please try again later." 429
        }
        
        handle @404 {
            respond "Page not found" 404
        }
        
        handle @500 {
            respond "Internal server error" 500
        }
    }
}

example.com {
    log {
        output file /var/log/caddy/access.log {
            roll_size 100MB
            roll_keep 10
        }
        format single_field common_log
        level INFO
    }
    
    # Enhanced IP blocking with network ranges
    @blocked_networks {
        remote_ip 192.0.2.0/24 198.51.100.0/24 203.0.113.0/24
    }
    respond @blocked_networks "Access Denied" 403
    
    # Block common attack patterns
    @attack_patterns {
        query union select drop insert update delete
    }
    respond @attack_patterns "Malicious request detected" 403
    
    # Block suspicious request methods
    @suspicious_methods {
        method TRACE OPTIONS CONNECT
    }
    respond @suspicious_methods "Method not allowed" 405
    
    # Comprehensive security headers
    header {
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        X-XSS-Protection "1; mode=block"
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; child-src 'none'; frame-src 'none'; worker-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "geolocation=(), microphone=(), camera=()"
        X-Permitted-Cross-Domain-Policies "none"
        -Server
        -X-Powered-By
    }
    
    # Request size and timeout limits
    request_body {
        max_size 10MB
    }
    
    # Multi-tier rate limiting system
    rate_limit {
        # Tier 1: Burst protection (very short window)
        zone burst_guard {
            key {remote_host}
            events 5
            window 1s
        }
        
        # Tier 2: Short-term protection
        zone short_term {
            key {remote_host}
            events 20
            window 10s
        }
        
        # Tier 3: Medium-term protection
        zone medium_term {
            key {remote_host}
            events 100
            window 1m
        }
        
        # Tier 4: Long-term protection
        zone long_term {
            key {remote_host}
            events 500
            window 5m
        }
        
        # Specialized zones
        zone api_endpoints {
            key {remote_host}
            events 30
            window 1m
        }
        
        zone auth_endpoints {
            key {remote_host}
            events 5
            window 1m
        }
        
        zone upload_endpoints {
            key {remote_host}
            events 3
            window 1m
        }
    }
    
    # Apply multi-tier rate limiting to all requests
    rate_limit burst_guard
    rate_limit short_term
    rate_limit medium_term
    rate_limit long_term
    
    # Path-specific additional limits
    @api path /api/*
    @auth path /login /register /auth/ /password-reset
    @upload path /upload /files/ /media/*
    
    rate_limit @api api_endpoints
    rate_limit @auth auth_endpoints
    rate_limit @upload upload_endpoints
    
    root * /var/www/html
    file_server
}

Enable and start Caddy service

Start Caddy and enable it to run automatically on system boot.

sudo systemctl daemon-reload
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy

Create test web content

Add some basic content to test the rate limiting and security features.

sudo tee /var/www/html/index.html > /dev/null << 'EOF'



    Caddy Security Test


    

Caddy Server with DDoS Protection

This server is protected by Caddy's rate limiting and security features.

EOF sudo chown -R caddy:caddy /var/www/html

Configure monitoring and alerting

Set up log monitoring

Create a script to monitor Caddy logs for rate limiting events and potential attacks.

#!/bin/bash

Log file paths

ACCESS_LOG="/var/log/caddy/access.log" ALERT_LOG="/var/log/caddy/security-alerts.log" EMAIL="admin@example.com"

Function to send alert

send_alert() { local message="$1" echo "$(date): $message" >> "$ALERT_LOG" # Uncomment to send email alerts # echo "$message" | mail -s "Caddy Security Alert" "$EMAIL" }

Monitor for rate limiting events

if [ -f "$ACCESS_LOG" ]; then # Check for 429 (Too Many Requests) responses in the last minute recent_429s=$(tail -n 1000 "$ACCESS_LOG" | grep "$(date -d '1 minute ago' '+%d/%b/%Y:%H:%M')" | grep -c " 429 ") if [ "$recent_429s" -gt 10 ]; then send_alert "High rate limiting activity detected: $recent_429s requests blocked in the last minute" fi # Check for blocked IPs (403 responses) recent_403s=$(tail -n 1000 "$ACCESS_LOG" | grep "$(date -d '1 minute ago' '+%d/%b/%Y:%H:%M')" | grep -c " 403 ") if [ "$recent_403s" -gt 5 ]; then send_alert "Suspicious activity detected: $recent_403s blocked requests in the last minute" fi # Check for potential DDoS (high request volume from single IPs) high_volume_ips=$(tail -n 5000 "$ACCESS_LOG" | grep "$(date '+%d/%b/%Y:%H:%M')" | awk '{print $1}' | sort | uniq -c | awk '$1 > 50 {print $2 " (" $1 " requests)"}') if [ -n "$high_volume_ips" ]; then send_alert "High volume requests detected from IPs: $high_volume_ips" fi fi
sudo chmod +x /etc/caddy/security/monitor-attacks.sh

Set up automated monitoring cron job

Schedule the monitoring script to run every minute to detect attacks quickly.

sudo crontab -e

Add this line to run the monitoring script every minute:

    * /etc/caddy/security/monitor-attacks.sh

Create IP blocking automation script

Set up a script that can automatically block IPs that exceed rate limits.

#!/bin/bash

ACCESS_LOG="/var/log/caddy/access.log"
BLOCKED_IPS_FILE="/etc/caddy/security/blocked-ips.txt"
CADDYFILE="/etc/caddy/Caddyfile"

Find IPs with more than 1000 requests in the last hour

offending_ips=$(tail -n 10000 "$ACCESS_LOG" | grep "$(date -d '1 hour ago' '+%d/%b/%Y:%H')" | awk '{print $1}' | sort | uniq -c | awk '$1 > 1000 {print $2}') for ip in $offending_ips; do # Check if IP is already blocked if ! grep -q "^$ip$" "$BLOCKED_IPS_FILE" 2>/dev/null; then echo "$ip" >> "$BLOCKED_IPS_FILE" echo "$(date): Auto-blocked IP $ip for excessive requests" >> /var/log/caddy/security-alerts.log # Update Caddyfile with new blocked IP # This is a simple approach - for production, consider a more sophisticated method sed -i "/remote_ip.*203\.0\.113\.0\/24/a\ remote_ip $ip" "$CADDYFILE" # Reload Caddy configuration systemctl reload caddy fi done
sudo chmod +x /etc/caddy/security/auto-block.sh

Test rate limiting and security features

Test basic rate limiting

Use curl to test that rate limiting is working correctly by making rapid requests.

# Test burst protection (should start blocking after 5 requests per second)
for i in {1..10}; do
    curl -w "%{http_code}\n" -o /dev/null -s http://example.com/
done

Test API rate limiting

for i in {1..25}; do curl -w "%{http_code}\n" -o /dev/null -s http://example.com/api/test sleep 1 done

Test security headers

Verify that security headers are being added to responses.

curl -I http://example.com/

Check for specific security headers

curl -s -D- http://example.com/ | grep -E "X-Frame-Options|X-Content-Type-Options|Strict-Transport-Security"

Test malicious request blocking

Verify that suspicious requests are being blocked properly.

# Test SQL injection attempt blocking
curl -w "%{http_code}\n" "http://example.com/?id=1 UNION SELECT * FROM users"

Test suspicious User-Agent blocking

curl -w "%{http_code}\n" -H "User-Agent: sqlmap/1.0" http://example.com/

Test missing User-Agent blocking

curl -w "%{http_code}\n" -H "User-Agent:" http://example.com/

Verify your setup

# Check Caddy service status
sudo systemctl status caddy

Verify Caddy configuration syntax

sudo caddy validate --config /etc/caddy/Caddyfile

Check rate limiting is working

curl -w "Status: %{http_code}, Time: %{time_total}s\n" -o /dev/null -s http://example.com/

View recent access logs

sudo tail -n 20 /var/log/caddy/access.log

Check security alerts

sudo tail -n 10 /var/log/caddy/security-alerts.log

Test security headers

curl -I http://example.com/ | grep -E "X-Frame-Options|Strict-Transport-Security|Content-Security-Policy"

Common issues

Symptom Cause Fix
Caddy won't start Configuration syntax error sudo caddy validate --config /etc/caddy/Caddyfile
Rate limiting not working Module not loaded or incorrect syntax Check order rate_limit before basicauth in global options
All requests getting blocked Rate limits too restrictive Increase events count in rate limit zones
Logs not being written Permission issues or disk space sudo chown -R caddy:caddy /var/log/caddy
Security headers not appearing Header directive not in right location Move header block inside site configuration
Auto-blocking script not working Script permissions or cron not running sudo chmod +x /etc/caddy/security/auto-block.sh
Note: For production deployments, consider implementing more sophisticated monitoring solutions like log-based monitoring with Grafana and Loki for comprehensive attack detection and response.

Next steps

Running this in production?

Need this managed for you? Setting up Caddy rate limiting once is straightforward. Keeping it monitored, updated, and tuned across environments while responding to evolving threats is the harder part. See how we run infrastructure like this for European SaaS and e-commerce teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.