corsproxy.dev vs cors.sh: an honest comparison
Both managed CORS proxies, both running on Cloudflare's edge, both with public source. Unlike most competitors on our wider comparison, we could actually go read cors.sh's code instead of just quoting their marketing page — so this one has receipts.
Disclosure: corsproxy.dev is our product. cors.sh figures come from their pricing page, homepage, and their own public source repo (specifically SPEC.md, which they describe as "the source of truth for how the service behaves") as of the date above.
At a glance
| cors.sh | corsproxy.dev | |
|---|---|---|
| Free tier | 10,000 requests, 5 GB / month | 500 requests / day (about 15,000 / month), 250 MB / day |
| Paid plan | Pro $4/month — 500,000 requests, 500 GB, no hourly cap | Pro $5/month — 20,000 requests/day (~600k/month), 10 GB/day |
| Auth model | Key is public by design; the browser's Origin header is the real authenticator for live_ keys | API key is the secret; origin allowlist enforced separately per key |
| Rate limiting | Durable Object, but only on test_ keys (30/10s); live_ keys have no short-window cap, only the monthly quota | Durable Object, atomic, on every request regardless of key type |
| Blocks private-network targets (SSRF) | No — not in their documented request checks (origin, target allowlist, size, rate; no IP/network filtering listed) | Yes — checked on the resolved IP of every connection, including redirects |
| Response size cap enforcement | 6 MB, checked via Content-Length; their own spec flags the no-Content-Length case as an open gap ("mid-stream cutoff when length absent ⛳") | Enforced regardless of whether the upstream declares a length |
| Open source / self-host | Yes, MIT — but a full monorepo (proxy worker + Next.js console + Stripe billing + Auth.js), designed to run as their own SaaS, not a drop-in self-host | Yes, MIT — a single Go binary, one file, deploy anywhere with one command |
| Keep third-party API keys out of the browser | Not offered | Yes: managed upstream headers |
| Request URL | proxy.cors.sh/<url> + x-cors-api-key header | api.corsproxy.dev/proxy?url=URL&key=KEY |
Where cors.sh is ahead
- A bigger free tier. 10,000 requests/month against our ~15,000/month — close, but their 5 GB bandwidth allowance is more generous than our 250 MB/day (~7.5 GB/month), so for bandwidth-heavy free-tier use they're ahead.
- Streaming architecture. Their spec describes the proxy streaming responses byte-for-byte with flat memory, rather than buffering. Our own OSS Go proxy buffers up to the size cap to guarantee an oversized response gets a clean error instead of a silent truncation — a real tradeoff, not a strict downgrade, but theirs is architecturally lighter on memory per request.
- Origin-pinning as the primary auth. Treating the key as public and the browser's unforgeable
Originheader as the real boundary is a legitimate design — it means a leakedlive_key is useless from anywhere but your own domains, no rotation needed. Different tradeoff than ours (key is the secret), not objectively worse.
Where corsproxy.dev is ahead
SSRF protection isn't optional here
We read cors.sh's own SPEC.md — every request check they document is listed: origin pinning, per-key target allowlists, the 6 MB size cap, rate limiting. Private-network and internal-address filtering isn't on that list. That means, as documented, a live_ key with no allowedTargets restriction could be pointed at http://169.254.169.254/ or an internal service and the proxy would fetch it. corsproxy.dev checks every target against the private/internal ranges on the resolved IP of every connection, including redirects, so a DNS-rebinding attempt can't get around it either. See how a CORS proxy can be abused for the full argument.
Rate limiting that doesn't have a paid-tier gap
cors.sh's Durable Object rate limiter is scoped to test_ keys only — their spec is explicit that live_ keys (the ones actually used in production) skip the short-window limiter entirely and rely on the monthly quota alone. That's a deliberate latency tradeoff on their end, but it means a compromised or misconfigured live key has no per-second brake until it's burned through a much larger monthly allowance. Every corsproxy.dev request, on every plan, hits the same atomic per-account limiter.
Actually self-hostable
cors.sh's source being public is genuinely notable — most competitors on our wider list aren't open at all. But their repo is the whole product: a Next.js console, Stripe billing, Auth.js accounts, D1 and KV bindings, built to run their own SaaS rather than to be dropped onto someone else's infrastructure in five minutes. Our proxy is one Go file with no external service dependencies — go run main.go or a single Docker container, and you're done. Both are honest "yes, open source" answers; they're different distances from your infrastructure.
Managed upstream headers
If your frontend calls an API that needs a secret key, corsproxy.dev can attach it server-side per request so it never ships to the browser. cors.sh's model doesn't have an equivalent — see calling OpenAI, Notion, and Google Sheets without leaking your API key.
Switching between them
const target = 'https://api.example.com/data';
// cors.sh
fetch('https://proxy.cors.sh/' + target, {
headers: { 'x-cors-api-key': 'live_YOUR_CORS_SH_KEY' }
});
// corsproxy.dev
fetch(`https://api.corsproxy.dev/proxy?url=${encodeURIComponent(target)}&key=YOUR_CORSPROXY_DEV_KEY`);
Note the different auth shape: cors.sh sends the key as a header with the target unencoded in the path; corsproxy.dev takes the target as a URL-encoded query parameter with the key alongside it (or as an X-API-Key header for server-side callers). Not a drop-in swap either direction — budget a few minutes to update the call site, not just the base URL.
Which one to pick
- You want the biggest free bandwidth allowance and don't need SSRF filtering: cors.sh.
- You're proxying to targets you don't fully control, or want a documented SSRF stance: corsproxy.dev.
- You want to self-host in one command, not stand up a console + billing + auth stack: corsproxy.dev.
- Your frontend needs to call an API with a secret key: corsproxy.dev, for managed upstream headers.
- You control the API you're calling: neither. Add CORS headers on your server; see how to enable CORS in Express.
Try it
Get a free API key — 500 requests/day, no credit card.