“Geofencing” is the practice of restricting some part of your service based on where the user is. The classic examples are streaming services (different catalogs per country), gambling and gaming (legal in some jurisdictions, not others), regulated financial products (compliance-driven), and physical product e-commerce (shipping limitations).
Every one of these has a tempting one-line solution: “look up the IP, check the country, allow or block.” And every one of these has failure modes that bite teams in production. This post walks through the basics, the inevitable edge cases, and the right way to architect geofencing so you don’t get sued, lose customers, or both.
What Geofencing Actually Means in Code
At its simplest, geofencing is:
on request:
country = lookup_country(request.client_ip)
if country not in allowed_countries:
return 403
That’s the kindergarten version. The real-world version involves:
- Multiple sources of “user location” (IP, GPS, billing address, account preference, declared shipping country).
- Different policies per type of content (free trial available everywhere, paid signup in some countries, withdrawal of funds in fewer).
- Edge cases (travel, VPNs, mobile carriers, satellite internet, embassies, military bases — all real).
- Legal nuance (what does “restricted in this country” actually require, and from when?).
- User experience (a blank 403 page is the worst possible answer).
Let’s go through each layer.
The Core Mechanic: IP-to-Country
Every IP address sits in a country, in the sense that whoever was assigned the address registered it under a country with a Regional Internet Registry. For most IPs, this country corresponds to where the user actually is — most of the time.
You can get this country from:
- A hosted IP geolocation API (recommended for accuracy and freshness). The Ip2Geo API returns
country.codeandcountry.namein every response. - An offline GeoIP database like MaxMind GeoLite2 or GeoIP2.
- CDN-injected headers (Cloudflare’s
CF-IPCountry, AWS CloudFront’sCloudFront-Viewer-Country).
For a discussion of the trade-offs between these approaches, see How to Geolocate an IP Address. The TL;DR for geofencing specifically:
- CDN headers are great when you’re already on the CDN and only need country-level data.
- Hosted APIs are great when you also want city, ASN, or VPN detection.
- Offline databases are great when you can’t add a network call and you accept the operational overhead of weekly updates.
For most teams setting up geofencing for the first time, the CDN header (if available) is the simplest. Add an enrichment API later when you need richer signals.
The Edge Cases You’ll Hit
You can build a country lookup in 20 minutes. You’ll spend the next two months handling these:
1. Travel
Your paying customer goes on vacation. Their IP changes country. Suddenly they can’t log in.
The fix: separate “where is the user right now” from “where is the user from.” Authentication and account access should not depend on current IP country. Country restrictions should apply to specific actions (purchase, content viewing) where the legal requirement actually matters.
For specific high-stakes actions (gambling stake, regulated financial transaction), the restriction should apply at point of action. For account access — never.
2. VPNs and Proxies
A user in a blocked country uses a VPN to access your service from an allowed country. Or vice versa — a user in an allowed country uses a VPN that exits in a blocked country.
The honest answer: you can’t fully solve this with IP alone. VPN detection catches commercial VPNs but not all. Apple Private Relay, Cloudflare Warp, corporate VPNs, mobile carrier gateways — they all look “VPN-ish” to detection systems.
For high-stakes geofencing (gambling, financial), the industry combines IP geo with declared identity verification, device fingerprinting, and behavioral signals. Anything less is a fig leaf.
For lower-stakes geofencing (streaming catalogs, country-specific content), accept that determined users will get around it and don’t panic about the 2% who do.
3. Mobile Carrier Gateways
A user is physically in France but their mobile carrier routes traffic through a gateway in Germany. Geo lookup says Germany. The user looks like they’re in Germany.
There’s no perfect fix. The mitigations:
- Use city-level data where available — sometimes the routing pattern is detectable.
- Combine IP geo with other signals (language preference, billing country) and use the most restrictive for restriction decisions and the most permissive for access decisions.
- Accept that mobile users will sometimes look like they’re in the wrong country.
4. CGNAT and Shared Public IPs
Carrier-Grade NAT means many users share one public IP. The IP’s geo location is the carrier gateway’s location, which can be far from any individual user. Less common in countries with abundant IPv4 but very common in mobile networks worldwide.
Same mitigation as above — don’t rely solely on IP geo for high-stakes decisions.
5. Anycast and CDN-Hosted Services
Some IPs are anycast — the same IP routes to different physical servers depending on the requester’s location. These IPs don’t have a meaningful single country. Geo databases handle them inconsistently. Rare, but you’ll see them occasionally in API integrations.
6. New, Reassigned, or Mobile Allocations
Cellular providers, satellite ISPs (Starlink), and new IP ranges all change country mappings over time. A database from six months ago might say “Country: US” for an IP that’s now serving users in Brazil. This is why we strongly recommend hosted APIs for production geofencing — the data is always current.
Designing the Policy Layer
Don’t put if country in blocked_list calls scattered across your codebase. Centralize the policy.
A clean structure:
class GeofencePolicy:
def signup_allowed(self, country: str) -> bool
def can_view_content(self, content_id: str, country: str) -> bool
def can_transact(self, transaction_type: str, country: str) -> bool
def can_withdraw(self, country: str) -> bool
def get_message(self, denial_reason: str, country: str) -> str
Each method takes a country (and possibly other context) and returns a decision plus a user-facing message. All the rules live in one place. Changes go through one file. Testing is straightforward — assert each (country, action) → decision pair.
When you inevitably need to add “actually, France can do X now,” it’s one edit, not a grep across the codebase.
The User Experience of Being Blocked
A blank 403 is the worst possible UX. A user who is wrongly blocked has no recourse. A user who is correctly blocked has no idea why.
A good blocked-state UX:
- Tells them why. “This content is not available in your country” is better than “Access denied.”
- Tells them which country was detected. Helps them realize a VPN is mis-detecting them.
- Offers next steps. Contact support, change your account country, sign up to be notified when available.
- Logs the event. You want to see false-positive patterns.
- Doesn’t lock them out of unrelated features. If shipping to their country is unavailable, they can still browse, save a wishlist, contact support.
Consider the difference between:
403 Forbidden
and
We’ve detected your connection coming from Germany. This service is currently available in the United States, Canada, and the UK. Get notified when we launch in your country: [email field]
The second one keeps a wronged user from churning and gives a real user a path forward.
The Legal Angle
Geofencing is often required by:
- Gaming regulators. Online gambling is licensed per-jurisdiction, with strict requirements about who can play.
- Streaming licensing. Music and video rights are often per-country.
- Financial services. Securities, derivatives, and crypto products have country-specific regulations.
- Export controls. Some technology cannot be sold to certain countries.
- Data residency. Personal data of EU residents (under GDPR) has handling requirements regardless of where the company is. See GDPR and IP Addresses for more.
If you’re geofencing for compliance, the IP check is the start, not the whole answer. Regulators care about the actual location of the user and the actual transaction — not just what the IP looks like. Multiple-signal verification (IP + declared address + payment method + identity verification) is the industry standard for anything serious.
Get a lawyer for any geofencing decision driven by regulation. The IP layer is the implementation; the legal layer is the requirement.
Performance: Don’t Make Every Request Wait
If geofencing adds 100ms to every page load, your bounce rate will hate you. Some patterns to keep it fast:
- Cache the lookup per-IP. Geo doesn’t change minute to minute. Cache the country lookup for at least 60 seconds (much longer is fine for country-level data).
- Do it at the edge. A reverse proxy or edge function looks up the country, sets a header, and your origin reads the header. Origin doesn’t pay any lookup cost.
- Use CDN headers when you can. If you’re on Cloudflare or CloudFront, the country header is already on the request — zero extra cost.
- Be selective. You don’t need to geo-check static assets, browsing pages, or read-only public APIs. Only the actions where the country actually matters.
What “Doing Geofencing Right” Actually Looks Like
A production-quality geofencing setup, condensed:
- Country comes from one source — your edge function or middleware sets
request.countryfrom IP geo on every request. All downstream code reads this field. - Policy lives in one place — a single
GeofencePolicyclass or module that owns all the rules. - The policy is data-driven — allowed/blocked country lists live in config, not in code. Updates don’t require a deploy.
- Decisions are made at action points — not at login, not at page load. The user can browse; the action gets blocked if it must be.
- UX is informative — users blocked from a specific action see a clear, friendly message with a path forward.
- High-stakes actions use multi-signal verification — IP country alone is not enough for compliance-driven blocks.
- Logs let you spot false positives — every blocked action gets a row in a log, and the log gets reviewed.
That’s the version that survives contact with real users and real regulators.
Putting It Together
Start simple, add layers as needed:
- Day 1: Use CDN country headers or a single API call to enrich every request with
country. Wire up aGeofencePolicyclass. Apply to one specific action (say, the “Buy” button). - Week 1: Add proper denial UX. Add logging. Add a config-driven country list.
- Month 1: Add VPN/proxy detection to the enrichment. Add multi-signal verification on high-stakes actions.
- Quarter 1: Review false-positive logs. Tune the country list. Add new restricted/allowed actions as compliance evolves.
The Ip2Geo API gives you country, city, ASN, and VPN detection in a single call, fast enough to run on every request. If you’re starting from zero, that single API call is enough to wire up Day 1 of the above. The rest is policy, UX, and operational discipline.