State Geo Block is a Traefik HTTP middleware that blocks non-US traffic and selected US subdivisions using a mounted compatible City-schema MMDB. It supports MaxMind GeoLite2-City and GeoIP2-City directly, as well as compact stategeodb compliance artifacts. It also supports trusted Cloudflare and proxy headers, direct connections, IPv4 and IPv6, explicit failure policies, database hot reload, and custom block pages.
CF-Connecting-IP, True-Client-IP,
X-Forwarded-For, RFC Forwarded, X-Real-IP, custom headers, or the
direct socket peer;blockNonUS is enabled;blockUSStates is
enabled;Forwarding headers are ignored unless the immediate RemoteAddr peer matches
trustedProxyCIDRs. This prevents an ordinary direct client from selecting its
own geography with a forged header.
Traefik also has a separate entry-point setting,
forwardedHeaders.trustedIPs, which controls whether it preserves incoming
X-Forwarded-* values. Configure both layers. With Kubernetes
externalTrafficPolicy: Cluster, the plugin usually sees a node-SNAT address,
so the node CIDR must be trusted and upstream header sanitization remains part
of the security boundary.
The safe default config trusts no proxy, enables only X-Forwarded-For,
rejects malformed trusted headers, and denies unresolved or non-public client
addresses. Provider-specific headers are opt-in. See
Client IP resolution for the full algorithm and
the Traefik-specific X-Real-IP behavior.
State Geo Block is listed in the Traefik Plugin Catalog. Configure a released tag in Traefik's static configuration; do not use a branch name in production.
experimental:plugins:stateGeoBlock:moduleName: github.com/vikewoods/traefik-plugin-state-geoversion: v1.2.0-rc.1
The alias stateGeoBlock is operator-selected but must be used consistently in
the dynamic Middleware configuration.
Mount a current City MMDB into every Traefik pod, configure Traefik's entry-point trusted IPs, and then create the Middleware:
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: state-geo-blocknamespace: traefik-systemspec:plugin:stateGeoBlock:dbPath: /data/geolite/GeoLite2-City.mmdbdatabaseReloadInterval: 1mcacheSize: 50000cacheTTL: 15mtrustedProxyCIDRs:- 10.17.1.0/24clientIPHeaders:- X-Forwarded-ForrejectInvalidClientIPHeaders: trueblockNonUS: trueblockUSStates: trueblockedStates: [CA, NY]databaseFailurePolicy: denylookupFailurePolicy: denyinvalidClientIPPolicy: denyunknownCountryPolicy: denyunknownSubdivisionPolicy: denyprivateIPPolicy: denylogLevel: infologClientIP: falsewhitelistedPaths:- /healthwhitelistedPathPrefixes:- /.well-known
10.17.1.0/24, /data/geolite/GeoLite2-City.mmdb, and the namespace above are
specific to the audited cluster and must be changed for other installations.
The complete Helm/PVC, Middleware, IngressRoute, standard Ingress, update, and
canary instructions are in Kubernetes deployment.
| Field | Default | Description |
|---|---|---|
dbPath | empty | Path inside the Traefik container to a compatible City-schema MMDB. |
databaseReloadInterval | 1m | Minimum interval between request-driven file replacement checks; minimum 1s. |
cacheSize | 1000 | Per-Middleware exact-IP LRU entry bound; 0 disables, maximum 100000. High-cardinality ingress can start near 50000 and tune from memory/hit-rate evidence. |
cacheTTL | 15m | Positive decision TTL when caching is enabled. |
clientIPHeaders | XFF | Ordered trusted client-IP sources. Cloudflare, True Client IP, Forwarded, X-Real-IP, and custom headers are opt-in. |
trustedProxyCIDRs | empty | Immediate peers permitted to supply client-IP headers. |
rejectInvalidClientIPHeaders | true | Send a present-but-invalid trusted header to invalidClientIPPolicy; false warns and tries the next source. |
blockNonUS | true | Deny known non-US countries. |
blockUSStates | true | Apply blockedStates and unknown-subdivision policy to US records. |
blockedStates | empty | Two-letter US subdivision codes to deny, normalized to uppercase. |
whitelistedIPs | empty | Exact IPv4/IPv6 addresses and CIDRs that bypass geography decisions. |
whitelistedPaths | empty | Normalized exact request paths that bypass all other decisions. |
whitelistedPathPrefixes | empty | Normalized, segment-safe path prefixes that bypass all other decisions. |
templateHTML | built-in | Inline escaped HTML template; supports {{STATE}} or {{.State}}. |
templatePath | empty | Mounted HTML template path; mutually exclusive with templateHTML. |
databaseFailurePolicy | legacy | allow, deny, error, or deprecated compatibility mode legacy. |
lookupFailurePolicy | allow | allow or deny when an MMDB lookup returns an error. |
invalidClientIPPolicy | deny | allow or deny when RemoteAddr or a strict trusted header is unusable. |
unknownCountryPolicy | allow | allow or deny when lookup returns no country. |
unknownSubdivisionPolicy | deny | allow or deny for a US record without a subdivision. |
privateIPPolicy | deny | allow, lookup, or deny for private, loopback, link-local, or unspecified clients. |
logLevel | info | off, error, warn, info, or debug; info records deny decisions without client IPs by default. |
logClientIP | false | Include resolved client IPs in structured logs. |
failOpen | true | Deprecated bridge used only by databaseFailurePolicy: legacy. |
Invalid state, IP/CIDR, path, duration, policy, template, and header configuration fails Middleware construction instead of being silently skipped. See Failure policies, Database lifecycle, and Middleware hardening for exact behavior.
The repository does not ship a production database. Operators may mount a MaxMind GeoLite2-City or GeoIP2-City database, or a compact stategeodb compliance artifact. The compact format supplies country data globally and the first subdivision only for US records, matching this middleware's country-first, US-state-only policy inputs; all allow, deny, unknown-data, and failure behavior still comes entirely from plugin configuration.
Mount the selected artifact read-only into Traefik and replace it atomically. The plugin detects a size or modification-time change, validates the replacement, retains the last known-good reader if reload fails, and invalidates cached decisions after a successful swap. See MMDB compatibility and Database lifecycle for the exact schema, validation evidence, cache generation behavior, and memory model. GeoLite2 users remain responsible for the MaxMind GeoLite terms.
Published v1.1 configurations require migration because trusted headers, path prefixes, invalid rules, failure policies, and bundled data behavior changed. The release candidate also hardens defaults from the withdrawn v2 prereleases. Read Migration from v1 before upgrading.
go test ./...go test -race ./...go vet ./...golangci-lint run ./...govulncheck ./...gosec -quiet ./..../scripts/traefik-smoke-test.sh
The smoke test loads the source through Traefik v3.7.1 and v3.7.6 interpreted local-plugin runtimes and verifies real IPv4/IPv6 decisions. See Contributing and the release checklist.
State Geo Block is licensed under the Apache License 2.0. Third-party fixture and dependency licenses are documented in THIRD_PARTY_NOTICES.md.