CORS Debugger
Pull the real response headers from any URL, see whether a preflight is in play, find the exact thing that's broken, and copy a server config fix.
This CORS debugger tracks down a cross-origin failure fast, the kind that turns the console red right after a deploy. Point it at a target URL, tell it which origin is doing the calling, set the method, credentials mode and any custom headers, and it pulls the real response headers, works out whether a preflight is even in play, then names the exact spec rule that is broken. It catches the usual suspects: a wildcard paired with credentials, a missing OPTIONS handler, a header that never made it into Allow-Headers, a trailing slash that breaks a byte-for-byte origin match, a missing Vary: Origin that poisons a cache. Then it hands you a cautious, allow-list shaped config you can paste straight into nginx, Apache, Express, FastAPI or Django, complete with a clean 204 on the preflight. Your origins never leave the page.
Queries run through the PeopleAreGeek lookup service. We log nothing.
CORS preflight tester, response header diagnostic and config generator for nginx, Apache, Express and FastAPI
CORS has dragged me out of bed at 2 a.m. A deploy ships. The console goes red. The front end just can't reach the API anymore, and you're squinting at a header at midnight. So I built this. Point it at a URL, say which origin's doing the calling, and it pulls the real response headers, works out whether a preflight is even in play, then names the exact thing that's broken and hands you config you can paste into your server. Your origins never leave the page.
What CORS actually checks and where most teams trip
This CORS debugger settles the question that eats whole afternoons: why a request works everywhere except the browser. CORS decides whether a script on one origin gets to read a response from another origin, and the browser is the only thing doing that deciding. Not your server, which honestly is the bit that confuses everyone. It is why curl pulls the same URL down clean while the browser slams the door on you: same response, different referee. And CORS does not stop the request from leaving. The browser fires it off, your server runs it, the response comes back, and only then does the browser hide that response from the JavaScript that asked. So by the time "blocked by CORS" lands in your console, whatever that request did to your server, it already did. Read that twice if you are firing a DELETE.
The three culprits this tool detects
- Wildcard plus credentials: pairing
Access-Control-Allow-Origin: *withAccess-Control-Allow-Credentials: trueis flat-out illegal per the spec, and every modern browser refuses it. Kill the star and echo the actual requesting origin instead. - Missing preflight handler: any non-simple request (a PUT, a DELETE, a custom Content-Type, a custom header) fires an OPTIONS preflight before the real call, and your server has to answer it with the right Allow-Methods and Allow-Headers. Most frameworks will not hand you that for free.
- Cached preflight:
Access-Control-Max-Agetells the browser to remember the preflight result, so you fix your config and nothing changes until that cache ages out, and you sit there wondering why the fix did nothing.
The server config snippets and what they protect against
Pick whatever stack you are on. nginx, Apache, Express, FastAPI, Django: the config the tool hands back keeps the same cautious shape regardless. It echoes the request origin, but only when that origin is on an explicit allow-list. It sets Vary: Origin so a cache cannot hand one caller's response to somebody else. It answers the OPTIONS preflight with a clean 204, and it stays nowhere near the wildcard. An allow-list is just the right default now. If your server fields cross-origin calls from one or two front ends you actually own, it has no business returning *, not even on the endpoints you mentally file as public. Save the wildcard for stuff that is genuinely open and stateless: a font CDN, a public price ticker, the kind of API where everything that matters already sits in the URL.
How to deploy CORS changes without breaking traffic
Be warier with CORS changes than with most config tweaks. The check runs in the browser, so a bad header crashes nothing loudly. It just quietly breaks the slice of users whose browsers happen to enforce the exact rule you botched. Ship to staging and run the same browser test you would run in prod, because Safari gets weird at the edges in ways Chrome and Firefox do not. Watch your OPTIONS count in the access logs live as it rolls out: a sudden spike, or a cliff, usually points at a preflight cache problem or a handler that is asleep at the wheel. And keep Access-Control-Max-Age low while you are rolling, around 60 seconds, so a regression is something you can yank back instead of waiting on every browser's cache to age out. Stable for a while? Bump it back to 86400 and stop staring at logs.
Frequently asked questions
Why does my CORS work in Postman but not in the browser?
Because Postman isn't a browser, and the browser is the only thing that enforces CORS. A call working in Postman or curl tells you one thing: your server is willing to answer it. About whether a browser will let your JavaScript read that result? Nothing. So test in a real browser. And throw Safari in if you care about iOS, since it's the one that surprises you.
Should I use the wildcard for a public API?
Only if it's genuinely public. No auth, no per-caller state, and you're dead sure you'll never send credentials. The moment you need a cookie or an Authorization header from the browser, that wildcard becomes a wall and you're rewriting it as an allow-list regardless. So skip the future migration and just start with the allow-list. It's the same amount of config either way, honestly.
What is the difference between a simple and a non-simple request?
A simple request is a GET, a HEAD, or a POST whose Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain, carrying nothing but standard headers. Cross that line anywhere (a PUT, a DELETE, a Content-Type of application/json, an Authorization header) and you're non-simple, which means a preflight goes out first. The preflight is the fussier of the two. So that's usually exactly where it breaks.
Can I just put a CORS proxy in front of my API?
Poking around locally? Sure, knock yourself out. In production? Please don't. A proxy buys you latency and a brand-new single point of failure, plus one more security boundary you now have to harden and babysit forever. Just fix the API server to send the right headers. It's the same five lines of nginx you were about to write anyway, except this version is actually yours.
My CORS is fine but I see "Failed to fetch" in the console, what now?
That bare "Failed to fetch" with zero detail is the browser being unhelpful on purpose. Nine times in ten it is one of two things. Either the preflight got rejected outright (your server threw a 405 or a 500 on OPTIONS), or the connection never landed at all. DNS died. Mixed content got blocked. A firewall ate it. Open the Network tab and hunt for a red OPTIONS sitting right before the request that failed. That little red row is almost always the one.