corsproxy.dev vs cors.sh: a side-by-side comparison
Both managed CORS proxies, both running on Cloudflare's edge. Unlike our hosted service and most competitors on our wider comparison, cors.sh publishes its code, so this comparison cites their own spec rather than only their marketing page.
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; optional per-key origin lock (a browser-side protection only) |
| Rate limiting | Durable Object, but only on test_ keys (30/10s); live_ keys have no short-window cap, only the monthly quota | No short-window cap on any key; a per-account daily quota, counted atomically in a Durable Object |
| Blocks private-network targets (SSRF) | No: not in their documented request checks (origin, target allowlist, size, rate; no IP/network filtering listed) | Yes, with limits: literal private addresses are refused, hostnames are checked by a separate DNS lookup before the fetch (fails open if the resolver errors), and redirects aren't followed |
| 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 ⛳") | 10 MB; the response is buffered, so an oversized one fails with 502 RESPONSE_TOO_LARGE whether or not the upstream declares a length |
| Open source / self-host | Yes, MIT: the full product monorepo (proxy worker, Next.js console, Stripe billing, Auth.js), built to run as their SaaS rather than as a drop-in self-host | The hosted service isn't open source. We publish a separate, simpler open-source CORS relay (MIT, one Go file) you can self-host |
| 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 monthly free allowance instead of a daily one. On paper our free tier is larger: 500 requests/day is about 15,000 a month against their 10,000, and 250 MB/day is about 7.5 GB a month against their 5 GB. But ours is a daily cap that resets at 00:00 UTC, so you can never use more than 500 requests or 250 MB in one day. Theirs is counted per month, so uneven traffic (a launch day, a demo, a weekly batch job) isn't capped day by day. Check their pricing page for any short-window limit on free keys before you rely on that.
- Streaming architecture. Their spec describes the proxy streaming responses byte-for-byte with flat memory. corsproxy.dev buffers the whole response (up to 10 MB) so an oversized one fails with a clear 502
RESPONSE_TOO_LARGEinstead of arriving cut off. That costs memory per request; streaming is lighter. (Our open-source Go relay streams too, and it silently truncates a body over its size limit.) - Origin-pinning as the primary auth. Treating the key as public and the browser's
Originheader as the boundary is a legitimate design: other websites can't use a liftedlive_key from their visitors' browsers, so there's less pressure to rotate it. Like our own origin lock, it doesn't stop a script outside the browser that sets its ownOriginheader.
Where corsproxy.dev is ahead
SSRF filtering in the proxy itself
cors.sh's SPEC.md lists the request checks they run: origin pinning, per-key target allowlists, the 6 MB size cap, rate limiting. Private-network and internal-address filtering isn't on that list, so nothing in the proxy as documented stops a live_ key with no allowedTargets from being pointed at http://169.254.169.254/ or an internal hostname. The backstop for both services is the same: a Worker on Cloudflare's edge can't reach private networks.
corsproxy.dev adds a layer on top of that. Literal private, loopback, link-local and metadata addresses are refused with 403 BLOCKED_HOST, and hostnames are resolved through Cloudflare's security resolver first, so a name that points at a private address is refused too. That check has limits we'd rather state than hide: it's a separate lookup before the fetch, it fails open if the resolver errors, and because resolve-then-fetch isn't atomic, DNS rebinding could in principle race it. Upstream redirects aren't followed; a 3xx comes back to your code as is. See how a CORS proxy can be abused for the wider argument.
A small open-source relay you can run yourself
cors.sh's source being public is 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. Our hosted service isn't open source. What we publish is a simpler open-source CORS relay: one Go file with no external service dependencies, run with go run main.go or a single Docker container. It has no accounts, keys, daily quotas or managed upstream headers, and it forwards request headers (including Cookie and Authorization) as is, so review its settings before exposing it publicly.
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.
Rate limiting on both
Neither service puts a short-window brake on production keys. cors.sh's Durable Object limiter (30 requests per 10 seconds) applies only to test_ keys; live_ keys rely on the monthly quota. corsproxy.dev has no per-second limit on any key: every request counts against a per-account daily quota, enforced atomically in a Durable Object. The practical difference is the size of the bucket a misused key can drain before it stops.
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
- Your traffic is uneven and a monthly allowance fits it better than a daily one: cors.sh, after checking its free-tier limits.
- You're proxying to targets you don't fully control, or want a documented SSRF stance: corsproxy.dev.
- You want a minimal relay to self-host in one command, without accounts or billing: our open-source Go relay.
- 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.