Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use GeoIP2 once at the edge, make ordinary country decisions with NGINX map, reserve OpenResty Lua for policy that cannot be expressed statically, and include every response-varying dimension in your cache key. That combination blocks or routes traffic without putting a network lookup in every request, while preventing one country from receiving another country’s cached representation.
This guide shows a production-shaped configuration, the safety checks around it, and how to measure the trade-offs. IP geolocation is an estimate: VPNs, mobile carriers, proxies, and corporate egress can produce the wrong country. NGINX documentation does not establish a universal latency improvement, so measure your own traffic.
Choose the decision path before writing configuration
Keep the fast path deliberately boring. The edge should derive a normalized country or region code from a local MMDB file, then apply a native rule.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →| Requirement | Best fit | Why |
|---|---|---|
| Stable allow/deny list | GeoIP2 plus map |
Configuration is easy to audit and does not execute application code per request. |
| Nearest regional upstream | GeoIP2 plus an upstream-selection map |
Users can be routed to a regional server group; the actual latency benefit depends on your network and origin placement. |
| Signed policy, exceptions, or multiple signals | OpenResty access_by_lua_block |
Lua can combine country, account state, an external policy feed, and request attributes. |
| Shareable API responses | proxy_cache with a complete key |
Country, language, device, authorization state, and other representation dimensions remain isolated. |
NGINX Plus has documented GeoIP2 dynamic-module packaging and API or key-value capabilities. Open-source NGINX and OpenResty are sufficient for many static country policies; choose Plus when its operational APIs or commercial support justify the license.
#1 Best Overall
Prerequisites and data lifecycle
- Install a GeoIP2 country or city MMDB database and keep its file path stable. A country database is enough for country blocking; use a city database only when you actually need region or city attributes.
- Load the GeoIP2 dynamic module that matches your NGINX build. Keep the module, NGINX binary, and MMDB updates under the same change-control process.
- Define policy separately from data freshness. A database update changes the location estimate; a policy update changes who is allowed; a cache purge changes what representation is served. Automate and monitor these three operations independently.
- Decide what status communicates your policy. Use
403for a forbidden request. Use451only when a legal or regulatory restriction is the reason and your organization has chosen that disclosure.
Implement fast country blocking with GeoIP2 and map
The following example allows the United States, Canada, the United Kingdom, and Germany. Replace the database path and country list with your policy.
load_module modules/ngx_http_geoip2_module.so;
http {
geoip2 /etc/nginx/geoip/GeoIP2-Country.mmdb {
$geo_country country iso_code;
}
map $geo_country $country_allowed {
default 0;
US 1;
CA 1;
GB 1;
DE 1;
}
server {
listen 443 ssl;
server_name api.example.com;
if ($country_allowed = 0) {
return 403;
}
location / {
proxy_pass http://api_origin;
}
}
}
map performs a constant-time configuration lookup and gives you a single, reviewable policy table. The if above contains only a return, which is the safe form for access rejection. If you need a different response body, route denied traffic to a named location instead of adding rewrite logic to the if.
Route countries to regional upstreams
Routing is not the same as blocking. Map countries to a regional group, then map that group to an upstream name.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →http {
geoip2 /etc/nginx/geoip/GeoIP2-Country.mmdb {
$geo_country country iso_code;
}
map $geo_country $region_group {
default other;
US na; CA na; MX na;
GB eu; DE eu; FR eu;
JP apac; SG apac; AU apac;
}
map $region_group $selected_upstream {
default api_other;
na api_na;
eu api_eu;
apac api_apac;
}
upstream api_na { server na-1.internal:8080; server na-2.internal:8080; }
upstream api_eu { server eu-1.internal:8080; server eu-2.internal:8080; }
upstream api_apac { server apac-1.internal:8080; server apac-2.internal:8080; }
upstream api_other { server origin.internal:8080; }
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://$selected_upstream;
}
}
}
Use a region group only when every country in that group receives the same representation. If content, tax, inventory, or legal behavior differs by country, keep the country code as the cache dimension and, where necessary, as the routing dimension.
Use OpenResty Lua when static maps are not enough
OpenResty exposes the access phase through access_by_lua_block. It is appropriate for signed policy documents, customer-specific exceptions, time windows, or a decision that combines several request attributes.
lua_shared_dict policy 1m;
server {
listen 443 ssl;
server_name api.example.com;
access_by_lua_block {
local country = ngx.var.geo_country or ""
local policy = ngx.shared.policy
local blocked = policy:get("blocked:" .. country)
if blocked == "1" then
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
location / {
proxy_pass http://api_origin;
}
}
Populate the shared dictionary from a refresh worker or another asynchronous mechanism. Do not perform an unbounded blocking network call in the access phase. Bound connection, read, and total policy-refresh times; retain the last known good policy when a refresh fails; and expose the policy version in logs so an incident can be correlated with a rollout.
Lua modules loaded with require are cached. Keep Lua code caching enabled in production: OpenResty’s reference documentation says, “Disabling the Lua code cache is strongly discouraged for production use and should only be used during development as it has a significant negative impact on overall performance.” With code caching enabled, source edits require an NGINX reload. Use a reload-safe deployment process and validate syntax before replacing workers.
Recommended Free Tools
Build an API cache that cannot cross country boundaries
Start with the origin’s cache policy. Honor Cache-Control, Vary, and related headers unless you have documented a deliberate override. Never share a response that is personalized, authenticated, unsafe, or otherwise non-shareable.
Separate cacheable methods and bypass conditions
http {
proxy_cache_path /var/cache/nginx/api
keys_zone=api_cache:100m
max_size=10g
inactive=10m
use_temp_path=off;
map $request_method $skip_method {
default 1;
GET 0;
HEAD 0;
}
map $http_authorization $skip_auth {
default 1;
"" 0;
}
map $cookie_session $skip_session {
default 1;
"" 0;
}
geoip2 /etc/nginx/geoip/GeoIP2-Country.mmdb {
$geo_country country iso_code;
}
map $geo_country $cache_geo {
default other;
US US;
CA CA;
GB GB;
DE DE;
}
server {
location /v1/catalog {
proxy_cache api_cache;
proxy_cache_methods GET HEAD;
proxy_cache_bypass $skip_method $skip_auth $skip_session;
proxy_no_cache $skip_method $skip_auth $skip_session;
proxy_cache_key "$scheme|$host|$request_method|$uri|$args|$cache_geo|$http_accept_language";
proxy_pass http://api_origin;
}
}
}
The key includes scheme, host, method, normalized URI, query string, country segment, and language. Add device class, experiment assignment, or another dimension whenever it changes the representation. Conversely, do not add arbitrary headers: unnecessary dimensions multiply objects and lower the hit rate.
Normalize query parameters deliberately
Using the raw $args is safe but can create a large number of equivalent objects when parameter order or tracking parameters vary. Build a normalized query value with map or application-level canonicalization, retaining only parameters that affect the response. Test that canonicalization never removes a functional parameter.
Choose country or region as the key
A region key has fewer objects and can improve reuse when all countries in the region truly share a representation. A country key provides stronger isolation. Omitting geography altogether risks serving one country’s content, price, inventory, or legal text to another country. Cache cardinality, hit rate, lock contention, and purge scope should be measured rather than guessed.
Free tools Windows power users keep installed
One-click scans. No signup required.
Protect misses and purges
For hot keys, configure cache locking so concurrent misses do not stampede the origin. Set bounded connect, send, and read timeouts. Purge or version cache objects when policy, localization, inventory, or legal content changes; waiting for TTL alone can leave stale data visible. NGINX Cookbook guidance covers cache keys, locking, bypass, purging, and performance as separate controls rather than one universal setting.
Put the pieces together and deploy safely
- Install or update the MMDB file and verify its permissions for the NGINX worker user.
- Load GeoIP2 and define the country variable before any
mapthat uses it. - Apply a native allow/deny map. Add a separate region map only for routing or cache segmentation that has been verified against product behavior.
- Add Lua only for decisions that require data or logic unavailable in static configuration. Keep refresh work asynchronous and bounded.
- Define cache bypass rules before defining the cache key. Test authenticated, session-cookie, non-GET, and error responses as explicitly non-shareable unless your application proves otherwise.
- Run
nginx -t. If it succeeds, reload withnginx -s reload; a reload lets existing workers finish while new workers use the new configuration. - Exercise requests from representative egress points, including an allowed country, a denied country, a VPN or proxy, and a request with authorization. Confirm status, upstream choice, cache status, and policy version in logs.
Observe correctness, speed, and cost
- Lookup cost: local MMDB lookup is predictable; Lua adds execution and any policy-store dependency to the access path.
- Cache cardinality: every added dimension creates more objects. Country isolation improves correctness but can reduce hit rate.
- Origin protection: cache locking, bounded timeouts, and bypass rules prevent a cache miss from becoming an origin surge.
- Freshness: monitor MMDB age, policy version propagation, and cache age separately.
- Business impact: track denial rates, regional upstream distribution, cache-hit and bypass rates, origin errors, and purge completion. A sudden denial spike may indicate a stale database or malformed policy, not a traffic attack.
- Geolocation limits: IP-based location can be wrong for VPNs, mobile networks, proxies, and corporate gateways. There is no universal accuracy or latency percentage to apply to every deployment.
Troubleshooting common failures
Every request is denied
Log the derived country variable and compare it with the MMDB’s expected result. Check that the module loaded, the database path is readable, and the map uses two-letter uppercase ISO codes. If a proxy hides client IPs, correct the trusted proxy chain before geolocation.
Users receive another country’s response
Inspect the cache key and response-varying headers. Add country or region, language, device, experiment, or authorization state when it changes the representation. Bypass caching for personalized responses and purge objects created with the old key.
Cache hit rate collapses
Look for high-cardinality query strings, unnecessary headers in the key, or a country key where a region key is genuinely safe. Normalize functional query parameters, remove tracking parameters, and compare hit rate against object count and correctness requirements.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteRank #4
Lua changes appear ignored
Production Lua code caching is expected. Run nginx -t, then nginx -s reload. Confirm the new worker generation is serving traffic and that the file loaded by require is the one you changed.
Origin load spikes during an outage
Check whether bypass rules, cache locking, or an origin-error policy is allowing every request through. Bound upstream timeouts, verify that only safe methods are cached, and decide explicitly how stale content should be handled during an origin failure.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If you need to verify how a regional, blocked, or consent-heavy page actually renders, ScreenshotNeo can capture it through one API request instead of maintaining a browser runner. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Use the API documentation at https://screenshotneo.com/docs/ for authentication and options. A minimal request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/geo-test -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/geo-test"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/geo-test' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo includes full-page and element capture, device and retina settings, custom headers and cookies, waits, request blocking, signed links, asynchronous jobs, bulk capture, PDF output, and a usage API on every plan. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Do I need a city database for country blocking?
No. A country MMDB supplies the ISO country code needed for a country policy. Use city data only when region or city-level attributes are part of a tested requirement.
Best Value
- Used Book in Good Condition
Should a cache key contain the HTTP method?
Include it when method semantics or the cache implementation could otherwise collide. In practice, configure caching for safe GET and HEAD requests and bypass other methods.
When is OpenResty preferable to native NGINX rules?
Choose it when decisions depend on signed or frequently changing policy, exceptions, or several signals. For a fixed country list, native maps have fewer moving parts.
Frequently Asked Questions
Can I use a 403 and a 451 response interchangeably?
No. Use 403 for a forbidden request. Reserve 451 for a restriction you intentionally identify as legal or regulatory.
What should I update first when a country policy changes?
Change the policy, then invalidate or version affected cache objects. MMDB freshness is a separate concern from policy propagation and cache invalidation.
Does NGINX Plus replace OpenResty Lua?
Not automatically. NGINX Plus adds documented packaging and API or key-value capabilities; OpenResty remains useful when programmable access-phase logic is the better fit.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitches

