1. Cloudflare (no code)
If your site runs through Cloudflare, every request carries the visitor’s country.
Block: Security → WAF → Custom rules, expression:
(ip.src.country in {"RU" "KP" "IR"})
with the action Block or Managed Challenge.
Read it in your app: Cloudflare adds the header CF-IPCountry: DE to requests sent to your origin, and Workers can read request.cf.country.
2. nginx with GeoIP2
Install the ngx_http_geoip2_module and a GeoIP2/GeoLite2 country database, then:
geoip2 /etc/nginx/GeoLite2-Country.mmdb {
$geoip2_country_code country iso_code;
}
map $geoip2_country_code $blocked_country {
default 0;
RU 1;
KP 1;
}
server {
if ($blocked_country) { return 451; }
}
Update the database file regularly; locally installed databases go stale within weeks.
3. Next.js middleware
Behind Vercel or Cloudflare, the country is in a request header:
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const country = req.headers.get("x-vercel-ip-country") ?? req.headers.get("cf-ipcountry");
if (country === "DE" && !req.nextUrl.pathname.startsWith("/de")) {
return NextResponse.redirect(new URL(`/de${req.nextUrl.pathname}`, req.url));
}
return NextResponse.next();
}
4. Any stack: an IP geolocation API
When you are not behind a CDN that adds the country, or need more than the country (city, VPN detection), look the IP up:
// Express
app.use(async (req, res, next) => {
const ip = req.headers["x-forwarded-for"]?.split(",")[0].trim() ?? req.socket.remoteAddress;
const r = await fetch(`https://api.ipbase.com/v2/info?ip=${ip}&fields=location.country.alpha2,security`, {
headers: { apikey: process.env.IPBASE_KEY },
});
const { data } = await r.json();
req.country = data?.location?.country?.alpha2;
req.isVpn = data?.security?.is_vpn;
next();
});
Cache the result per IP (for example for a day) so repeat visitors cost no extra request. Check your own address with What is my IP.