When a URL returns 404 but the page clearly exists, encoding is often the culprit: an encoded slash the server rejects, a double-encoded value, an unencoded # that truncates the path, or a stray space. Here’s a systematic way to find which one.
Before anything else, decode the failing URL so you can see what the server actually received. Decode recursively in case it’s double-encoded:
Failing: /search?q=hello%2520world
Decoded once: /search?q=hello%20world
Decoded twice: /search?q=hello world ← it was DOUBLE-encoded
If the value only reads correctly after two passes, something encoded an already-encoded string. Fix the code so it encodes exactly once, at the point the URL is built.
| Symptom | Cause | Fix |
|---|---|---|
404 on a path with %2F | Server rejects encoded slashes in the path by default | Move the value to the query string, or enable encoded-slash handling |
%2520 in the URL | Double-encoding (% got encoded to %25) | Encode once only |
Everything after # is missing server-side | Unencoded # starts the fragment (never sent to server) | Encode a literal # in data as %23 |
| 400/404 with a raw space | Space isn’t legal in a URL | Encode as %20 (path) or + (query) |
| Two “different” URLs, one 404s | Mixed hex casing or param order | Normalize casing; check routing is case-consistent |
Parse the failing URL into its parts and confirm the path is what your router matches on. A common surprise: an encoded slash that the framework decoded, turning one route segment into two.
const u = new URL(failingUrl);
u.pathname; // what the router sees
u.searchParams.toString(); // the parameters, decoded
// Compare pathname against your actual route pattern
If a parameter value legitimately contains & or # and wasn’t encoded, everything after it leaks out of the value — the server sees a different (often nonexistent) route or a truncated parameter. Encode & as %26 and # as %23 inside values. Building values with URLSearchParams does this for you.
Paste the failing URL into the URL parser to see its parts, and the decoder (recursive mode) to catch double-encoding. Our troubleshooting guide lists ten encoding bugs and their fixes.
Written and maintained by the urlencodedecode.com team. Every technical claim on this page is verified against primary sources — the RFCs (3986, 3629, 4648, 7578), the WHATWG URL Standard, and official vendor or language documentation — rather than second-hand summaries. When a source contradicts a common assumption, we follow the source and note the discrepancy. Corrections: contactus@urlencodedecode.com.
Encoding is a common cause: the server may reject an encoded slash (%2F) in the path, the value may be double-encoded (%2520), an unencoded # may have truncated the path, or a raw space made the URL invalid. Decode the failing URL to see what the server actually received.
Many servers and proxies reject a percent-encoded slash in the path by default, as a defence against path-traversal tricks, or decode it early and split one route segment into two. Move the value into the query string, or enable your server's encoded-slash handling if you truly need %2F in the path.
It's a double-encoded space: a space was encoded to %20, then that string was encoded again, turning the % into %25 to give %2520. Decode twice to confirm, then fix the code so it encodes the value exactly once at the point the URL is built.
An unencoded # starts the fragment, which the browser never sends to the server, so everything after it disappears server-side. An unencoded & inside a value ends that parameter early. Encode # as %23 and & as %26 when they're data — URLSearchParams does this automatically.
Decode the failing URL (recursively, to catch double-encoding), then parse it into path and query and compare the path against your actual route pattern. Check for encoded slashes, an unencoded # or & splitting a value, raw spaces, and mixed hex casing.