IPFilter Caddy Plugin

A Caddy v2 plugin that provides geolocation-based request filtering using the jpillora/ipfilter library with IP2Location LITE data. Perfect for restricting access to specific countries without requiring external databases or API keys.
β¨ Features
- π Zero Configuration: No database downloads, API keys, or external dependencies required
- π Country-Based Filtering: Allow or deny requests based on visitor country
- π CIDR Block Support: Allow or deny specific IP addresses and CIDR ranges (IPv4 and IPv6)
- π Free Geolocation Data: Uses IP2Location LITE database (completely free)
- β‘ High Performance: Embedded geolocation data with no network calls
- π‘οΈ Thread-Safe: Safe for concurrent request handling
- π Full Caddy Support: JSON and Caddyfile configuration support
- π Easy Integration: Drop-in Caddy HTTP matcher
π¦ Installation
Option 1: Download Pre-built Binary
Download a Caddy binary with this plugin pre-installed from the releases page.
Option 2: Build from Source
-
Install xcaddy (if not already installed):
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
-
Build Caddy with the plugin:
xcaddy build --with github.com/jpillora/ipfilter-caddy
-
Replace your existing Caddy binary:
sudo cp caddy /usr/bin/caddy
sudo systemctl restart caddy
Option 3: Add to Existing Installation
If you have Caddy installed via package manager:
caddy add-package github.com/jpillora/ipfilter-caddy
π Usage
JSON Configuration
{
"apps": {
"http": {
"servers": {
"example": {
"listen": [":443"],
"routes": [
{
"match": [
{
"ipfilter_geolocation": {
"allow_ips": ["10.0.0.0/8", "192.168.0.0/16"],
"allow_countries": ["AU", "US", "CA"],
"block_by_default": true
}
}
],
"handle": [
{
"handler": "static_response",
"body": "Welcome!"
}
]
},
{
"handle": [
{
"handler": "static_response",
"status_code": 403,
"body": "Access restricted"
}
]
}
]
}
}
}
}
}
Caddyfile Configuration
example.com {
@allowed_countries {
ipfilter_geolocation {
allow_countries AU US CA
block_by_default true
}
}
handle @allowed_countries {
respond "Welcome from Australia, USA, or Canada!"
}
handle {
respond "Access restricted to specified countries" 403
}
}
# Block specific countries
api.example.com {
@blocked_countries {
ipfilter_geolocation {
deny_countries RU CN
}
}
handle @blocked_countries {
respond "Access denied from restricted countries" 403
}
# Continue with normal processing for allowed countries
reverse_proxy localhost:8080
}
βοΈ Configuration Options
JSON Fields
| Field |
Type |
Default |
Description |
allow_ips |
[]string |
[] |
List of IP addresses or CIDR blocks to allow |
deny_ips |
[]string |
[] |
List of IP addresses or CIDR blocks to deny |
allow_countries |
[]string |
[] |
List of ISO country codes to allow |
deny_countries |
[]string |
[] |
List of ISO country codes to deny |
block_by_default |
bool |
false |
Block all requests by default unless explicitly allowed |
Caddyfile Directives
ipfilter_geolocation {
allow_ips <ip_or_cidr...>
deny_ips <ip_or_cidr...>
allow_countries <country_codes...>
deny_countries <country_codes...>
block_by_default <true|false>
}
Rule Precedence
Rules are evaluated in the following order (highest to lowest priority):
- Explicit IP matches - Single IP addresses checked first
- CIDR subnet matches - Allow takes precedence over deny within subnets
- Country code matches - Geolocation-based rules
- Default behavior -
block_by_default setting applies if no rules match
π Country Codes
Uses standard ISO 3166-1 alpha-2 country codes:
AU - Australia
US - United States
CA - Canada
GB - United Kingdom
DE - Germany
FR - France
JP - Japan
CN - China
RU - Russia
- And many more...
Note: Unknown or private IPs (like 127.0.0.1, ::1) return country code ZZ.
π Examples
1. Australia-Only Access
australia-only.example.com {
@australia {
ipfilter_geolocation {
allow_countries AU
block_by_default true
}
}
handle @australia {
respond "G'day! Welcome from Australia π¦πΊ"
}
handle {
respond "This site is only accessible from Australia" 403
}
}
2. Block Bad Actors
api.example.com {
@restricted {
ipfilter_geolocation {
deny_countries RU CN KP
}
}
handle @restricted {
respond "Access denied" 403
}
reverse_proxy localhost:3000
}
3. Multi-Country Support
global.example.com {
@eu_countries {
ipfilter_geolocation {
allow_countries DE FR GB IT ES NL
block_by_default true
}
}
handle @eu_countries {
respond "Welcome from Europe! πͺπΊ"
}
handle {
respond "This content is geo-restricted to European countries" 403
}
}
4. CIDR Block Filtering
Allow internal networks and specific countries while blocking everything else:
internal.example.com {
@allowed {
ipfilter_geolocation {
allow_ips 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 ::1/128
allow_countries AU US
block_by_default true
}
}
handle @allowed {
reverse_proxy localhost:3000
}
handle {
respond "Access denied" 403
}
}
5. Mixed IP and Country Rules
Block specific IP ranges while allowing certain countries:
api.example.com {
@allowed {
ipfilter_geolocation {
deny_ips 203.0.113.0/24 198.51.100.0/24
allow_countries AU US CA GB
block_by_default true
}
}
handle @allowed {
reverse_proxy localhost:8080
}
handle {
respond "Forbidden" 403
}
}
6. Debug Logging
Enable debug logging to see IP-to-country mappings:
{
"logging": {
"logs": {
"default": {
"level": "debug"
}
}
}
}
This will show logs like:
{"level":"debug","logger":"http.matchers.ipfilter_geolocation","msg":"IPFilter geolocation check","ip":"1.2.3.4","country":"AU","allowed":true}
π§ Technical Details
Plugin Architecture
- Module ID:
http.matchers.ipfilter_geolocation
- Type: HTTP Request Matcher
- Dependencies: jpillora/ipfilter (with embedded IP2Location LITE data)
How It Works
- IP Extraction: Gets the client IP from Caddy's request context
- Geolocation Lookup: Uses embedded IP2Location database to find country
- Rule Evaluation: Applies allow/deny rules based on configuration
- Request Routing: Continues or blocks request processing
- Lookup Time: Sub-millisecond geolocation lookups
- Memory Usage: ~50MB for embedded database
- Thread Safety: Safe for concurrent access
- No External Calls: All data is local
Accuracy Notes
IP geolocation is inherently approximate:
- Mobile networks may show incorrect locations
- VPNs and proxies can mask real locations
- Corporate networks often appear in headquarters country
- Accuracy is typically 95%+ for broadband connections
π§ͺ Testing
Manual Testing
-
Start your server:
caddy run --config your-config.json
-
Test from different locations:
# Test from current location
curl https://your-domain.com/
# Test with VPN (if available)
# Connect to VPN in different country and test again
-
Check logs for geolocation data:
caddy run --config your-config.json 2>&1 | grep "IPFilter geolocation"
Automated Testing
package main
import (
"net/http"
"net/http/httptest"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/jpillora/ipfilter-caddy"
)
func TestAustraliaFilter(t *testing.T) {
// Create matcher
matcher := &IPFilterGeolocation{
AllowCountries: []string{"AU"},
BlockByDefault: true,
}
// Mock Australian IP request
req := httptest.NewRequest("GET", "/", nil)
req = req.WithContext(caddyhttp.WithRemoteAddr(req.Context(), "1.1.1.1:12345")) // Cloudflare AU IP
if !matcher.Match(req) {
t.Error("Expected Australian IP to be allowed")
}
}
π€ Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
Development Setup
-
Clone the repository:
git clone https://github.com/jpillora/ipfilter-caddy.git
cd ipfilter-caddy
-
Install dependencies:
go mod download
-
Build and test:
go build
go test ./...
-
Build with Caddy:
xcaddy build --with ./...
Guidelines
- Follow Go best practices
- Add tests for new features
- Update documentation
- Ensure thread safety
- Keep dependencies minimal
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments
π Support
Made with β€οΈ for the Caddy community