Guide · Jul 13, 2026

Double Encoding: How a URL Gets Encoded Twice by Accident

A space that should become %20 shows up as %2520 instead. A redirect target with a query string arrives broken, its own ? and & mangled into more percent-codes on top of the ones that were already there. Both are the same underlying bug: something got percent-encoded twice, once too many, somewhere between where the value originated and where it's finally used.

Why %2520 is the signature of the bug

Percent-encoding a raw space produces %20. Percent-encoding %20 a second time treats that literal % character as data needing its own escape — % itself encodes to %25 — so %20 becomes %2520. Any time you see %25 immediately followed by what looks like another valid percent-code, that's close to a guaranteed sign of double encoding, since a value wouldn't naturally contain a literal, un-encoded % character followed by two hex digits under normal circumstances.

Where it actually happens

The most common cause is a pipeline with more than one layer that each independently decide "this needs to be safe for a URL" and each apply their own encoding step, unaware the other already did. A frontend form encodes a value before sending it; a backend framework's URL-building helper encodes it again before constructing a redirect; a reverse proxy or CDN rewrites the URL and re-encodes it a third time. Each layer in isolation is doing something reasonable — encoding a value before putting it in a URL is correct — the bug only exists in the combination, when nobody realizes a previous layer already handled it.

The related, opposite bug: decoding too many times

The reverse mistake happens too — a value gets decoded more than once, which is more dangerous than it sounds. A value that was legitimately encoded once, safely, can decode into something meaningfully different (and potentially unsafe) if decoded an extra time, since a doubly-decoded string can expose characters that were deliberately escaped to prevent exactly that. This matters most in security-sensitive contexts, like a URL parameter that gets used to build a file path or a database query — decoding it an unexpected extra time can reintroduce characters an earlier layer specifically encoded to neutralize.

How to actually track it down

Decode the value once and look at the result. If it's now correct plain text, one layer over-encoded it. If it's still got percent-codes in it, decode again and check — but note how many times it took, since that tells you how many layers in the pipeline are each adding their own encoding step. The fix isn't "add another decode step to compensate" (that just moves the bug around and makes the pipeline fragile to any future change), it's finding which specific layer's encoding is redundant and removing it there, so the value passes through encoded exactly once.

A rule that prevents most of this upfront

Encode a value exactly once, as close as possible to the point where it's actually inserted into a URL — not earlier, "just in case," and not by multiple independent layers each making their own defensive assumption. If a value arrives already encoded from an external source (a link a user pasted, a redirect target from another service), decode it first to get back to the true raw value before deciding whether it needs encoding again for your own use.

Check your own encoding

FreeToolDev's bulk URL encoder/decoder makes it easy to decode a suspicious value once, look at the result, and decode again if needed — a quick way to count exactly how many layers of encoding a broken value has accumulated before tracking down where each one is coming from.