Bypass Geo-Blocking with Cloudflare Workers (No VPN Needed)
Access geo-restricted APIs without a VPN. Deploy a Cloudflare Worker reverse proxy in minutes and route requests through edge nodes near your target.
Some government APIs and public data portals are only accessible from specific countries. If you’re building a project that needs data from a geo-restricted source — say, a government registry that blocks non-domestic IPs — you’ll hit a wall fast. Requests time out, return 403s, or silently drop.
Cloudflare Workers solve this neatly. They execute at Cloudflare’s edge, which means your request originates from a data centre in or near the target country. No VPN, no rented VPS, no SSH tunnels. Just a few lines of JavaScript running on Cloudflare’s free tier.
🌍 Why Geo-Blocking Exists
Government portals and public APIs often restrict access by IP geolocation for several reasons:
- Compliance — data residency requirements mandate domestic access only
- Abuse prevention — blocking foreign IPs reduces bot traffic and scraping
- Infrastructure protection — limiting access to domestic users reduces load
- Regulatory scope — some data is only legally distributable within a jurisdiction
This isn’t about bypassing security. If the data is public within the target country, you’re just routing your request through an edge node closer to the source.
⚙️ How Cloudflare Workers Help
Cloudflare has data centres in over 300 cities across 100+ countries. When you deploy a Worker, it runs at the edge — meaning requests to your Worker URL are handled by the nearest Cloudflare node. If you’re fetching data from a geo-restricted government portal, the Worker fetches it from a Cloudflare node near the target country, not from your laptop on the other side of the world.
The architecture is simple:
Your App → Cloudflare Worker (edge) → Target API (geo-restricted)
↓
Response flows back
🛠️ Setting Up a Cloudflare Worker Proxy
Prerequisites
- A free Cloudflare account (dash.cloudflare.com)
- Node.js installed (for the Wrangler CLI)
- Basic familiarity with JavaScript
Step 1: Install Wrangler
npm install -g wrangler
wrangler login
Step 2: Create the Worker
mkdir cf-proxy && cd cf-proxy
wrangler init --type javascript
Step 3: Write the Proxy
Replace src/index.js (or worker.js) with:
// Geo-proxy worker — forwards requests to a target URL
// Usage: https://your-worker.workers.dev/?url=https://target-api.go.id/endpoint
const ALLOWED_ORIGINS = [
"https://yourdomain.com",
"http://localhost:3000",
];
const ALLOWED_DOMAINS = [
"api.example.go.id",
"data.example.go.id",
];
export default {
async fetch(request) {
// CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
headers: corsHeaders(request),
});
}
const url = new URL(request.url);
const targetUrl = url.searchParams.get("url");
if (!targetUrl) {
return new Response(
JSON.stringify({ error: "Missing ?url= parameter" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
// Validate target domain
const targetHost = new URL(targetUrl).hostname;
if (!ALLOWED_DOMAINS.some((d) => targetHost.endsWith(d))) {
return new Response(
JSON.stringify({ error: "Domain not in allowlist" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
}
// Forward the request
const response = await fetch(targetUrl, {
method: request.method,
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DataProxy/1.0)",
Accept: "application/json, text/html",
},
});
// Return with CORS headers
const body = await response.text();
return new Response(body, {
status: response.status,
headers: {
...Object.fromEntries(response.headers),
...corsHeaders(request),
},
});
},
};
function corsHeaders(request) {
const origin = request.headers.get("Origin") || "";
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
return {
"Access-Control-Allow-Origin": allowed,
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Max-Age": "86400",
};
}
Step 4: Deploy
wrangler deploy
You’ll get a URL like https://cf-proxy.your-account.workers.dev. Test it:
curl "https://cf-proxy.your-account.workers.dev/?url=https://api.example.go.id/data"
Step 5: Customise
- Add domains to
ALLOWED_DOMAINSfor each geo-restricted API - Restrict origins in
ALLOWED_ORIGINSto your actual frontend/backend domains - Add rate limiting via Cloudflare’s built-in rate limiting rules
- Add caching with
caches.defaultfor responses that don’t change often
🤖 Setting It Up with an AI Chatbot
You don’t need to write this from scratch. AI coding assistants like Claude or ChatGPT can generate, debug, and deploy Cloudflare Workers for you. Here’s how:
Using Claude (claude.ai or Claude Code)
Paste a prompt like this:
“Create a Cloudflare Worker that acts as a reverse proxy. It should accept a
?url=query parameter, validate the target domain against an allowlist, forward the request, and return the response with CORS headers. The allowed domains are: api.example.go.id, data.example.go.id. Restrict CORS to my domain https://myapp.com.”
Claude will generate the full worker.js, wrangler.toml, and deployment commands. You can iterate:
“Add caching for GET requests with a 5-minute TTL.” “Add a rate limit of 10 requests per minute per IP.” “Make it handle POST requests too, forwarding the request body.”
Using ChatGPT
Same approach — describe what you need:
“I need a Cloudflare Worker that proxies requests to geo-restricted government APIs. It should have a domain allowlist, CORS support, and return JSON errors for invalid requests.”
Then deploy what it generates using wrangler deploy.
Using Claude Code (CLI)
If you use Claude Code, you can do the whole thing in one shot:
claude "Create a Cloudflare Worker proxy for geo-restricted APIs.
Target domains: api.bmkg.go.id, pom.go.id.
My frontend: https://myapp.com.
Include wrangler.toml. Deploy it."
Claude Code will create the files, write the code, and run wrangler deploy for you.
🔒 Security Considerations
A proxy like this needs guardrails:
- Domain allowlist is mandatory — without it, your Worker becomes an open proxy that anyone can abuse
- CORS restriction — only allow your own domains, never
*in production - Rate limiting — Cloudflare offers built-in rate limiting rules; configure them
- No sensitive headers — don’t forward authentication tokens from the original request
- Monitor usage — Cloudflare’s free tier gives you 100,000 requests/day; check the dashboard
- Don’t proxy login pages — only proxy public data endpoints, not authentication flows
📊 Cloudflare Workers Free Tier Limits
| Resource | Free Tier |
|---|---|
| Requests | 100,000 / day |
| CPU time | 10ms per request |
| Worker size | 1 MB |
| Cron triggers | 5 |
| KV reads | 100,000 / day |
| KV writes | 1,000 / day |
For most data-fetching use cases, the free tier is more than enough.
🚨 When This Won’t Work
Cloudflare Workers can’t solve every geo-blocking scenario:
- Cloudflare-to-Cloudflare — if the target site also uses Cloudflare, the request may be flagged or blocked (Cloudflare recognises its own IPs). You’ll get 403s or 1xxx error codes.
- IP reputation blocking — some sites block known data centre IPs regardless of location
- JavaScript-rendered content — Workers can only fetch raw HTTP responses, not execute client-side JavaScript. For SPAs, you need a headless browser (Playwright, Puppeteer)
- Authentication-gated content — if the portal requires login or CAPTCHA, a simple proxy won’t help
- Legal restrictions — respect the terms of service. Public data is usually fine; scraping private content is not
💭 Final Thoughts
Geo-blocking is often a blunt instrument. Government portals that publish public data shouldn’t need to block international researchers, developers, or citizens abroad — but they do, usually as a side effect of security policies rather than intentional exclusion.
Cloudflare Workers offer a clean, free, low-maintenance workaround. A few lines of JavaScript, a domain allowlist, and you’re querying geo-restricted APIs as if you were in-country. The free tier handles most use cases, the security model is straightforward, and AI assistants can generate the entire setup in minutes.
The key is to use it responsibly: proxy public data, restrict access to your own applications, and respect rate limits.
Related Posts:
- Setting Up Tailscale for OpenClaw — Zero-config secure networking for AI agents
- Building a Personal Finance Site for Indonesia — Using Cloudflare Pages for global delivery