A Traefik middleware plugin that blocks or allows requests based on geographic location, using the IPInfo Lite database with automatic updates.
- Allowlist or blocklist mode via country codes (ISO 3166-1 alpha-2)
- Automatic database updates on a configurable interval
- Private/reserved IP handling — optionally pass through RFC1918 and loopback addresses
- Configurable fallback when an IP is not found in the database
- Custom HTTP status code for blocked requests (default:
403) - Optional request logging
- Country header — write
X-Geoblock-CountryandX-Geoblock-Cityon every response for visibility in Traefik access logs
Add the plugin to your Traefik static configuration:
experimental:
plugins:
geoblock:
moduleName: github.com/leardev/traefik-geoblock-plugin
version: v0.1.9Traefik 3.5 introduced unsafe plugin loading, which allows running plugins directly from a local directory without going through the Plugin Catalog. This is useful for self-hosted setups or forks:
# traefik.yml (static config)
experimental:
localPlugins:
geoblock:
moduleName: github.com/leardev/traefik-geoblock-plugin
# Mount the plugin source into the Traefik container:
# /plugins-local/src/github.com/leardev/traefik-geoblock-plugin/Note: Local/unsafe plugins are not signed or vetted by Traefik Labs. Only load plugin source you trust.
For local development, use localPlugins instead (see test/docker-compose.yml).
The plugin supports two database backends. Set either databaseMMDBPath (MMDB, default) or databasePath (CSV) — not both.
| Backend | Config key | Default path | Memory usage |
|---|---|---|---|
MMDB .mmdb |
databaseMMDBPath |
/tmp/ipinfo_lite.mmdb |
~50 MB heap per replica |
CSV .csv.gz |
databasePath |
(none) | ~150 MB heap per replica |
The MMDB backend is recommended for production and multi-replica deployments.
| Option | Type | Default | Description |
|---|---|---|---|
allowedCountries |
[]string |
— | Allowlist of country codes. Mutually exclusive with blockedCountries. |
blockedCountries |
[]string |
— | Blocklist of country codes. Mutually exclusive with allowedCountries. |
token |
string |
required | IPInfo API token for downloading the database. |
databaseMMDBPath |
string |
/tmp/ipinfo_lite.mmdb |
Path to cache the MMDB database. Mutually exclusive with databasePath. |
databaseMMDBURL |
string |
(IPInfo MMDB URL) | Override the MMDB download URL (testing only). |
databasePath |
string |
— | Path to cache the gzipped CSV database. When set, the CSV backend is used. Mutually exclusive with databaseMMDBPath. |
databaseURL |
string |
(IPInfo CSV URL) | Override the CSV download URL (testing only). |
updateInterval |
int |
24 |
Hours between automatic database refreshes. |
allowPrivate |
bool |
true |
Pass through requests from private/reserved IP ranges. |
defaultAllow |
bool |
true |
Allow requests when the IP is not found or the DB is not loaded. |
httpStatusCode |
int |
403 |
HTTP status code returned for blocked requests. |
logEnabled |
bool |
false |
Log each allowed/blocked decision. |
addCountryHeader |
bool |
false |
Write an X-Geoblock-Country response header with the resolved country code, and (MMDB backend only) an X-Geoblock-City header with the city name, on every request (see Access log visibility). |
Exactly one of allowedCountries or blockedCountries must be set.
http:
middlewares:
my-geoblock:
plugin:
geoblock:
allowedCountries:
- DE
- AT
- CH
token: "your-ipinfo-token"
databaseMMDBPath: "/data/ipinfo_lite.mmdb"
updateInterval: 24
allowPrivate: true
defaultAllow: false
logEnabled: truehttp:
middlewares:
my-geoblock:
plugin:
geoblock:
allowedCountries:
- DE
- AT
- CH
token: "your-ipinfo-token"
databasePath: "/data/ipinfo_lite.csv.gz"
updateInterval: 24
allowPrivate: true
defaultAllow: false
logEnabled: trueTraefik's access log shows the HTTP status code but not why a request was blocked. Enable addCountryHeader: true to write an X-Geoblock-Country response header on every request (plus X-Geoblock-City when using the MMDB backend), then tell Traefik to include them in access log lines.
Plugin config:
http:
middlewares:
my-geoblock:
plugin:
geoblock:
addCountryHeader: true
# ... other optionsTraefik static config:
accessLog:
fields:
headers:
defaultMode: drop
names:
X-Geoblock-Country: keep
X-Geoblock-City: keepBlocked access log lines will then include the country (and city, if available) that triggered the block:
"GET / HTTP/1.1" 403 ... "X-Geoblock-Country: CN" "X-Geoblock-City: Shanghai"
Header values:
| Header | Value | Meaning |
|---|---|---|
X-Geoblock-Country |
DE, US, … |
Resolved 2-letter country code |
X-Geoblock-Country |
unknown |
IP not found in the database |
X-Geoblock-Country |
private |
Request from a private / RFC 1918 address |
X-Geoblock-Country |
db-not-loaded |
Database not yet downloaded |
X-Geoblock-City |
Berlin, … |
City name (MMDB backend only; omitted when not available) |
The header is set on all requests — allowed and blocked alike — so it can also be used for metrics or access log analysis, not just debugging 403s.
Note: The Docker Compose setup in
test/is for integration testing only, not for production use.
IPINFO_TOKEN=your-token docker compose -f test/docker-compose.yml upTraefik will be available at http://localhost:80 and the dashboard at http://localhost:8080.
MIT