Skip to main content

Custom CIDR Notation

In addition to predefined IP ranges, Caddy Defender supports custom CIDR notation for precise IP blocking.

Basic Syntax

You can specify custom IP ranges directly in your Caddyfile:
defender block {
    ranges 203.0.113.0/24 198.51.100.0/24
}

CIDR Format

CIDR (Classless Inter-Domain Routing) notation consists of:
  • An IP address: 203.0.113.0
  • A slash: /
  • A prefix length: 24
The prefix length indicates how many bits are used for the network portion:
  • /32 = Single IP address (e.g., 192.168.1.1/32)
  • /24 = 256 IP addresses (e.g., 192.168.1.0/24)
  • /16 = 65,536 IP addresses (e.g., 192.168.0.0/16)
  • /8 = 16,777,216 IP addresses (e.g., 10.0.0.0/8)

Validation

Custom CIDR ranges are validated during configuration loading. The validation logic is in config.go:228-240:
for _, ipRange := range m.Ranges {
    // Check if the range is a predefined key (e.g., "openai")
    if _, ok := data.IPRanges[ipRange]; ok {
        // If it's a predefined key, skip CIDR validation
        continue
    }
    
    // Otherwise, treat it as a custom CIDR and validate it
    _, _, err := net.ParseCIDR(ipRange)
    if err != nil {
        return fmt.Errorf("invalid IP range %q: %v", ipRange, err)
    }
}

What Gets Validated

If a range matches a key in the IPRanges map (like openai, aws, etc.), it’s used directly without validation.
defender block {
    ranges openai  # ✓ Valid - predefined key
}
If not found in the predefined map, the value is parsed as CIDR notation using Go’s net.ParseCIDR.
defender block {
    ranges 203.0.113.0/24  # ✓ Valid CIDR
    ranges 198.51.100.42/32  # ✓ Valid single IP
}
Invalid CIDR notation or unknown keys will cause configuration errors.
defender block {
    ranges 203.0.113.0  # ✗ Invalid - missing prefix
    ranges unknown-key  # ✗ Invalid - not in IPRanges map
    ranges 256.1.1.1/24  # ✗ Invalid - invalid IP address
}

Combining Predefined and Custom Ranges

You can mix predefined keys with custom CIDR blocks:
defender block {
    # Combine OpenAI ranges with custom IPs
    ranges openai 203.0.113.0/24 198.51.100.0/24
}
defender block {
    # Block cloud providers plus specific troublesome IPs
    ranges aws gcloud azure 45.79.19.0/24 104.131.0.0/16
}

IPv6 Support

Caddy Defender supports both IPv4 and IPv6 CIDR notation:
defender block {
    # IPv4 ranges
    ranges 203.0.113.0/24
    
    # IPv6 ranges
    ranges 2001:db8::/32
    
    # Both together
    ranges 203.0.113.0/24 2001:db8::/32
}
Many predefined ranges (like gcloud, cloudflare) already include both IPv4 and IPv6 ranges.

Common CIDR Examples

Single IP

ranges 192.168.1.100/32
Blocks exactly one IP address

Small Network

ranges 192.168.1.0/24
Blocks 256 addresses (192.168.1.0 - 192.168.1.255)

Medium Network

ranges 10.0.0.0/16
Blocks 65,536 addresses (10.0.0.0 - 10.0.255.255)

Large Network

ranges 10.0.0.0/8
Blocks 16,777,216 addresses (10.0.0.0 - 10.255.255.255)

Real-World Examples

Block Specific Datacenter

defender block {
    # Block a specific hostile datacenter range
    ranges 45.79.19.0/24 104.131.0.0/16
}

Block Internal Network Access

defender block {
    # Block access from your internal networks
    ranges 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
}

Geographic Blocking (with external list)

defender block {
    # Block IP ranges from a specific country
    # (ranges obtained from external source)
    ranges 203.0.113.0/24 198.51.100.0/24 192.0.2.0/24
}

Temporary Block During Attack

defender block {
    # Quickly block attacking IPs during incident
    ranges 198.51.100.42/32 203.0.113.17/32
}

CIDR Tools and Resources

Test if an IP is in a CIDR range:
# Using ipcalc
ipcalc 203.0.113.0/24 -c 203.0.113.15

# Using Python
python -c "import ipaddress; print('203.0.113.15' in ipaddress.ip_network('203.0.113.0/24'))"

Performance Considerations

Optimal Performance: Custom CIDR ranges are processed the same way as predefined ranges, so there’s no performance difference. However:
  • Fewer, larger CIDR blocks are more efficient than many individual IPs
  • Use /32 for single IPs sparingly - combine into larger blocks when possible
  • Consider creating a predefined range for frequently used custom blocks

Creating Your Own Predefined Ranges

If you have a set of custom ranges you use frequently, you can add them to the build process:
  1. Create a new fetcher in ranges/fetchers/:
    package fetchers
    
    type MyCustomFetcher struct{}
    
    func (f MyCustomFetcher) Name() string {
        return "mycustom"
    }
    
    func (f MyCustomFetcher) Description() string {
        return "My custom IP ranges"
    }
    
    func (f MyCustomFetcher) FetchIPRanges() ([]string, error) {
        return []string{
            "203.0.113.0/24",
            "198.51.100.0/24",
        }, nil
    }
    
  2. Add it to ranges/main.go:34-55:
    fetchersList := []fetchers.IPRangeFetcher{
        // ... other fetchers
        fetchers.MyCustomFetcher{},
    }
    
  3. Rebuild the ranges:
    go run ranges/main.go
    
  4. Use it in your Caddyfile:
    defender block {
        ranges mycustom
    }
    

Build docs developers (and LLMs) love