Geo Block Middleware
The Geo Block middleware provides country-based access control for routes, allowing or denying requests based on the client’s country resolved from a GeoIP database. It applies globally to the route, so there is no need to configure path-level restrictions.
Overview
The geoBlock middleware resolves the client’s country from its IP address (using the configured GeoIP database) and enforces a policy:
- ALLOW: only the listed countries may access the route (allowlist).
- DENY: the listed countries are blocked; everyone else is allowed (blocklist).
It can also enrich the upstream request with the resolved country header, so your backend can localize content, prices, or behavior without shipping its own GeoIP database.
Privacy note: the client IP is used only to look up the country at the edge and is never forwarded — only the ISO country code is.
Prerequisites
The middleware requires a GeoIP database in MaxMind .mmdb format. Both MaxMind (GeoLite2-Country.mmdb) and IP2Location (IP2LOCATION-*.MMDB) are supported — both expose a country.iso_code.
Set the path with the GOMA_GEOIP_DB environment variable (default /etc/goma/GeoLite2-Country.mmdb):
GOMA_GEOIP_DB=/etc/goma/GeoLite2-Country.mmdb
If the database is absent or unreadable, country resolution is disabled and the middleware follows its allowUnknown setting (fail-open by default), so a missing database never locks everyone out.
Configuration
Basic Structure
middlewares:
- name: <middleware-name>
type: geoBlock
rule:
action: <ALLOW|DENY>
countries:
- <ISO-3166-1-alpha-2>
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
action | String | Yes | — | Policy action. ALLOW (allowlist) or DENY (blocklist). |
countries | Array | Yes | — | List of ISO 3166-1 alpha-2 country codes (e.g. US, FR, DE). |
statusCode | Integer | No | 403 | HTTP status returned for a blocked request. |
message | String | No | Access denied from your region | Response body message for a blocked request. |
allowUnknown | Boolean | No | true | What to do when the country can’t be resolved (no database, private IP, lookup miss). true allows (fail-open); false blocks (fail-closed). |
addCountryHeader | String | No | — | When set, the resolved country is added to the upstream request under this header (e.g. X-Country-Code). |
Behavior
- Country codes are ISO 3166-1 alpha-2 and matched case-insensitively.
- Private and loopback clients bypass the check — internal, mesh, and health-check traffic (loopback, RFC 1918 private, link-local) is never geo-fenced.
- Unresolved country follows
allowUnknown. The default is fail-open, so an absent or unreadable database can’t block legitimate traffic. addCountryHeaderis applied to allowed requests too, making the middleware usable purely for enrichment (combine with a permissive rule).
Configuration Examples
Example 1: Allowlist (only these countries)
middlewares:
- name: eu-only
type: geoBlock
rule:
action: ALLOW
countries:
- FR
- DE
- ES
- IT
message: "This service is only available in the EU."
Example 2: Blocklist (deny specific countries)
middlewares:
- name: sanctions-block
type: geoBlock
rule:
action: DENY
countries:
- KP
- IR
statusCode: 451 # Unavailable For Legal Reasons
Example 3: Fail-closed (deny anything un-geolocated)
middlewares:
- name: strict-us-only
type: geoBlock
rule:
action: ALLOW
countries: [US]
allowUnknown: false # unresolved country is blocked
Example 4: Country enrichment for the upstream
addCountryHeader is applied to every allowed request, so any rule can double as country enrichment. Use a DENY blocklist with a country you don’t expect to see (so nothing is actually blocked) purely to attach the header:
middlewares:
- name: geo-header
type: geoBlock
rule:
action: DENY
countries: [ZZ] # not a real country ⇒ blocks nothing
addCountryHeader: X-Country-Code
The backend then reads
X-Country-Codeon every request without shipping its own GeoIP database.
Applying the Middleware
Reference the middleware by name on a route:
routes:
- name: my-app
path: /
hosts:
- app.example.com
backends:
- endpoint: http://backend:8080
middlewares:
- eu-only
Order matters: place geoBlock before authentication and rate-limiting middlewares so blocked regions are rejected as early as possible.
Metrics
Denied requests are counted by the Prometheus metric:
gateway_geoblock_denied_total{name="<middleware-name>", country="<ISO code>"}
Combined with gateway_requests_by_country_total, this lets you monitor how much traffic each rule rejects and from where.