This plugin was forked from nscuro/traefik-plugin-geoblock: traefik plugin to whitelist requests based on geolocation and remains compatible with the original plugin.
A Traefik plugin that allows or blocks requests based on IP geolocation using IP2Location database.
๐ This project includes IP2Location LITE data available from
lite.ip2location.com.
[!Tip]
Traefik Security
The basic middlewares you need to secure your Traefik ingress:
๐ Geoblock: david-garcia-garcia/traefik-geoblock - Block or allow requests based on IP geolocation
๐ก๏ธ CrowdSec: maxlerebourg/crowdsec-bouncer-traefik-plugin - Real-time threat intelligence and automated blocking
๐ ModSecurity CRS: david-garcia-garcia/traefik-modsecurity - Web Application Firewall with OWASP Core Rule Set
๐ฆ Ratelimit: https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/ratelimit/ - Control request rates and prevent abuse
Designed for high-performance production environments:
This architecture ensures consistent response times and eliminates external service bottlenecks, making it ideal for high-traffic environments and air-gapped deployments.
โ ๏ธ IMPORTANT REQUIREMENTS:
- Traefik v3.5.0 or later is required (unsafe operations support was introduced in v3.5.0)
- Unsafe operations must be enabled in Traefik configuration
It is possible to install the plugin locally or to install it through Traefik Plugins.
Create or modify your Traefik static configuration
experimental:localPlugins:geoblock:moduleName: github.com/david-garcia-garcia/traefik-geoblocksettins:useunsafe: true# REQUIRED: Enable unsafe operations for this pluginplugins:geoblock:settings:useunsafe: true
You should clone the plugin into the container, i.e
# Create the directory for the pluginsRUN set -eux; \mkdir -p /plugins-local/src/github.com/david-garcia-garciaRUN set -eux && git clone https://github.com/david-garcia-garcia/traefik-geoblock /plugins-local/src/github.com/david-garcia-garcia/traefik-geoblock --branch v1.0.1 --single-branch
Add to your Traefik static configuration:
experimental:plugins:geoblock:moduleName: github.com/david-garcia-garcia/traefik-geoblockversion: v1.0.1# REQUIRED: Enable unsafe operations for this pluginsettings:useunsafe: true
For automatic database updates to function, ensure your firewall allows outbound HTTPS connections to:
download.ip2location.comwww.ip2location.comNote: If automatic updates are disabled (
databaseAutoUpdate: false), no external network access is required and the plugin operates entirely offline.
You can spin up a fully working environment with docker compose:
docker compose up --build
The codebase includes a full set of integration and unit tests:
# Run unit testsgo test# Run integration tests.\Test-Integration.ps
The plugin supports the following environment variable for configuration:
TRAEFIK_PLUGIN_GEOBLOCK_PATH: Directory path used as fallback location for database and HTML files when they are not found in the specified paths or when paths are empty/omitted.Example usage:
# Docker Composeenvironment:- TRAEFIK_PLUGIN_GEOBLOCK_PATH=/data/geoblock# Docker rundocker run -e TRAEFIK_PLUGIN_GEOBLOCK_PATH=/data/geoblock traefik:latest# System environment variableexport TRAEFIK_PLUGIN_GEOBLOCK_PATH=/opt/traefik-plugins/geoblock
When this environment variable is set, the plugin will automatically look for IP2LOCATION-LITE-DB1.IPV6.BIN and geoblockban.html files in the specified directory if they are not found in their configured locations.
version: "3.7"services:traefik:image: traefik:v3.5.3 # v3.5.0 or later requiredcommand:# REQUIRED: Enable unsafe operations for geoblock plugin- "--experimental.plugins.geoblock.settings.useunsafe=true"volumes:- /var/run/docker.sock:/var/run/docker.sock- ./traefik.yml:/etc/traefik/traefik.yml- ./dynamic-config.yml:/etc/traefik/dynamic-config.yml- ./IP2LOCATION-LITE-DB1.IPV6.BIN:/plugins-storage/IP2LOCATION-LITE-DB1.IPV6.BINports:- "80:80"- "443:443"whoami:image: traefik/whoamilabels:- "traefik.enable=true"- "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"- "traefik.http.routers.whoami.middlewares=geoblock@file"
http:middlewares:geoblock:plugin:geoblock:#-------------------------------# Core Settings#-------------------------------enabled: true # Enable/disable the plugin entirelydefaultAllow: false # Default behavior when no rules match (false = block)#-------------------------------# Database Configuration#-------------------------------databaseFilePath: "/plugins-local/src/github.com/david-garcia-garcia/traefik-geoblock/IP2LOCATION-LITE-DB1.IPV6.BIN"# Can be:# - Full path: /path/to/IP2LOCATION-LITE-DB1.IPV6.BIN# - Directory: /path/to/ (will search for IP2LOCATION-LITE-DB1.IPV6.BIN recursively).# Use /plugins-storage/sources/ if you are installing from plugin repository.# - Empty: automatically searches using fallback locations (see below)## Fallback search order when file is not found:# 1. TRAEFIK_PLUGIN_GEOBLOCK_PATH environment variable directory#-------------------------------# Country-based Rules (ISO 3166-1 alpha-2 format)#-------------------------------allowedCountries: # Whitelist of countries to allow- "US" # United States- "CA" # Canada- "GB" # United KingdomblockedCountries: # Blacklist of countries to block- "RU" # Russia- "CN" # China#-------------------------------# Network Rules#-------------------------------allowPrivate: true # Allow requests from private/internal networks (marked as "PRIVATE")# This includes RFC 1918 private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)# and loopback addresses (127.0.0.0/8 for IPv4, ::1 for IPv6)allowedIPBlocks: # CIDR ranges to always allow (highest priority)- "192.168.0.0/16"- "10.0.0.0/8"- "2001:db8::/32"blockedIPBlocks: # CIDR ranges to always block- "203.0.113.0/24"# More specific ranges (longer prefix) take precedence# Directory-based IP blocks (loaded once during plugin initialization)# This is useful if you mount configmaps in your traefik plugin# so that these will be shared among all Geoip middleware instancesallowedIPBlocksDir: "/data/allowed-ips/" # Directory with .txt files containing allowed CIDR blocksblockedIPBlocksDir: "/data/blocked-ips/" # Directory with .txt files containing blocked CIDR blocks# All .txt files in the directory are scanned recursively during plugin startup# Each .txt file should contain one CIDR block per line (comments with # supported)# Note: Changes to files require plugin restart to take effect# Example file content:# # AWS IP ranges# 172.16.0.0/12# 203.0.113.0/24#-------------------------------# IP Extraction Configuration#-------------------------------ipHeaders: # List of headers to check for client IP addresses (required, cannot be empty)- "x-forwarded-for" # Default: check X-Forwarded-For header first- "x-real-ip" # Default: check X-Real-IP header second# Custom examples:# - "cf-connecting-ip" # Cloudflare# - "x-client-ip" # Custom proxy# - "remoteAddress" # SYNTHETIC: Maps to req.RemoteAddr (direct connection IP)## IMPORTANT: Header order matters! IPs are processed in the order headers are defined.# Within each header, IPs are processed left-to-right (leftmost = original client IP).# Duplicate IPs are automatically removed, preserving the first occurrence.## SYNTHETIC HEADERS:# - "remoteAddress": Special synthetic header that maps to req.RemoteAddr field# This provides access to the actual network connection's remote address# Useful when you need to check the direct connection IP alongside proxy headers## Example configurations:# ipHeaders: ["x-forwarded-for", "remoteAddress"] # Check proxy header first, then direct connection# ipHeaders: ["remoteAddress"] # Only check direct connection IP# ipHeaders: ["remoteAddress", "x-real-ip"] # Check direct connection first, then proxy headeripHeaderStrategy: "CheckAll" # Strategy for processing multiple IP addresses (default: CheckAll)# Options:# - "CheckAll": Check all IPs found in headers (original behavior)# - "CheckFirst": Check only the first IP address found# - "CheckFirstNonePrivate": Check first non-private IP, fallback to first private IP if no public IPs foundignoreVerbs: # List of HTTP verbs to ignore for blocking (still enriched with GeoIP)- "OPTIONS" # Common for CORS preflight requests- "HEAD" # Common for health checks# Additional examples:# - "TRACE" # HTTP TRACE method# - "CONNECT" # HTTP CONNECT method# Note: Verb matching is case-insensitive#-------------------------------# Bypass Configuration#-------------------------------bypassHeaders: # Headers that skip geoblocking entirelyX-Internal-Request: "true"X-Skip-Geoblock: "1"X-Cdn-Auth: "mysupersecretkey"#-------------------------------# Error Handling and ban#-------------------------------banIfError: true # Block requests if IP lookup failsdisallowedStatusCode: 403 # HTTP status code for blocked requests. If you are using banHtmlFilePath make sure to set this to a valid code (such as NOT 204).banHtmlFilePath: "/plugins-local/src/github.com/david-garcia-garcia/traefik-geoblock/geoblockban.html"# Can be:# - Full path: /path/to/geoblockban.html# - Directory: /path/to/ (will search for geoblockban.html recursively). Use /plugins-storage/sources/ if you are installing from plugin repository.# - Empty: returns only status code## Fallback search order when file is not found:# 1. TRAEFIK_PLUGIN_GEOBLOCK_PATH environment variable directory# Template variables available: {{.IP}} and {{.Country}}#-------------------------------# Logging Configuration#-------------------------------logLevel: "info" # Available: debug, info, warn, errorlogFormat: "json" # Available: json, textlogPath: "/var/log/geoblock.log" # Empty for Traefik's standard outputlogBannedRequests: true # Log blocked requests. They will be logged at info level.fileLogBufferSizeBytes: 1024 # Buffer size for file logging in bytes (default: 1024)fileLogBufferTimeoutSeconds: 2 # Buffer timeout for file logging in seconds (default: 2)# File logging uses buffered writes for better performance. The buffer is flushed when:# - The buffer reaches fileLogBufferSizeBytes size# - fileLogBufferTimeoutSeconds seconds have passed since the last flush# - The logger is closed/shutdown#-------------------------------# Database Auto-Update Settings#-------------------------------databaseAutoUpdate: true# Enable automatic database updates with hot-swapping. Updates check every 24 hours# and immediately on startup if the current database is older than 1 month.# Updated databases are hot-swapped without requiring middleware restart.# Make sure you whitelist in your FW domains ["download.ip2location.com", "www.ip2location.com"]databaseAutoUpdateDir: "/data/ip2database"# Directory to store updated databases. This must be a persistent volume in the traefik pod.# The plugin uses a singleton pattern - multiple middlewares with identical configurations# share the same database factory and hot-swap operations.databaseAutoUpdateToken: "" # IP2Location download token (if using premium)databaseAutoUpdateCode: "DB1" # Database product code to download (if using premium)#-------------------------------# Response header settings#-------------------------------countryHeader: "X-IPCountry"# Optional header to add the country code to the REQUEST (available in Traefik access logs)# This header is added to the request that gets forwarded to your backend service# You can use this to see where all your traffic is coming from in access logs# Example access log config: accesslog.fields.headers.names.X-IPCountry=keep# Note: Header is initially set to "PRIVATE" and only overridden by the first real country found# This ensures private IPs processed later cannot override legitimate country informationremediationHeadersCustomName: "X-Geoblock-Action"# Optional header to add the blocking phase/reason to the RESPONSE when request is blocked# This header is added to the HTTP response sent back to the client (available in Traefik access logs)# Possible values: "allow_private", "blocked_ip_block", "allowed_ip_block",# "blocked_country", "allowed_country", "default_allow", "error"# Example access log config: accesslog.fields.headers.names.X-Geoblock-Action=keep# When empty, no header is added to blocked responses
The plugin processes requests in the following order:
Important Notes:
CheckAll strategy: If any IP in the chain is blocked, the request is deniedCheckFirst or CheckFirstNonePrivate strategies: Only the selected IP(s) are evaluated; the request is denied only if the selected IP is blockedignoreVerbs skip all blocking logic but still receive GeoIP enrichmentWhen using JSON logging, the following fields are included in blocked request log entries (note: allowed requests are not logged):
time: Timestamp of the request in ISO 8601 formatlevel: Log level (debug, info, warn, error)msg: Log message describing the actionplugin: Plugin identifierip: The IP address that triggered the actionip_chain: Full chain of IP addresses from X-Forwarded-For headercountry: Country code or "PRIVATE" for internal networkshost: Request host headermethod: HTTP method usedphase: Processing phase where the action occurred:
allow_private: Private network checkblocked_ip_block: IP block rules check (blocked)allowed_ip_block: IP block rules check (allowed)blocked_country: Country rules check (blocked)allowed_country: Country rules check (allowed)default_allow: Default allow/deny rulepath: Request pathExample log entry:
{"time": "2025-03-01T19:24:04.414051815Z","level": "INFO","msg": "blocked request","plugin": "geoblock@docker","ip": "172.18.0.1","ip_chain": "","country": "PRIVATE","host": "localhost:8000","method": "GET","phase": "allow_private","path": "/bar"}
๐ This project is licensed under the Apache License 2.0 - see the LICENSE file for details.