Tools Nimbus

Why does my URL show %2520

Tools Nimbus is a free, no-signup developer toolkit that runs entirely in your browser, so your data is never uploaded to a server. A URL showing %2520 is not corrupted: it is a space that was percent-encoded twice, once to %20 and again to %2520. Paste it into the Tools Nimbus URL Encoder and Decoder, free with no signup, and decode once per layer until the value is clean.

Last updated July 2026

The arithmetic behind %2520

Percent-encoding replaces a character with a % followed by its two-digit hex byte value. A space is byte 0x20, so it becomes %20. The catch is that % is itself a reserved character: it is the escape marker, so it cannot appear literally in an encoded string. When a value needs to carry a real percent sign, that sign is encoded as %25, because 0x25 is the byte value of %.

Put those two rules together and double encoding is inevitable. Feed the already-encoded string %20 back into an encoder and it sees three characters: %, 2, and 0. The percent becomes %25, while 2 and 0 are unreserved and pass through untouched. The output is %25 + 20, which is %2520. Nothing malfunctioned. The encoder did exactly its job, on input that had already been through it once.

Counting the layers

Every additional pass inserts one more 25 immediately after the percent sign. That makes the encoding depth readable straight off the string, which is the fastest triage available:

Raw characterEncoded onceEncoded twiceEncoded three times
space%20%2520%252520
%%25%2525%252525
&%26%2526%252526
=%3D%253D%25253D
/%2F%252F%25252F

Decoding removes one 25 per pass. If you decode a value and the output still contains percent escapes that look like data rather than structure, decode again. When the output stops changing, you have reached the raw value and the number of passes you made is the number of encoders in your chain.

Symptom, cause, and fix at a glance

SymptomMost likely causeFix
Spaces render as %2520 in the address barA value was encoded, stored, then encoded again on renderStore raw values; encode once at URL construction
%3A%2F%2F where :// belongsThe whole URL was encoded instead of its partsEncode path segments and parameter values only
Query separators appear as %26 and %3DA query string was encoded as if it were one valueLet the query-string builder encode each value
A nested redirect_uri shows %252FA URL inside a parameter got encoded twice by designDecode once at the boundary that reads it
Server returns 404 on a path with a spaceProxy or gateway re-encoded an encoded pathNormalize once at the edge, not at every hop
Literal %2520 saved in the databaseEncoded input was persisted instead of the raw valueDecode on ingest; backfill affected rows

Cause 1: encoding a value that arrived already encoded

This is the overwhelming majority of cases. A parameter comes in over the wire percent-encoded, as it must be. Your framework decodes it for you into the raw value, or it does not, and this is where the confusion starts. Some routers hand you decoded values; some hand you the raw query string. Code that calls encodeURIComponent on a value it did not first confirm was decoded adds a second layer.

The tell is that the bug is invisible for ASCII-only inputs and appears the moment a user types a space, an ampersand, or an accented letter. A value like report survives any number of encoding passes unchanged, because every character in it is unreserved. Only when a reserved character enters the value does an extra layer become visible. That is why these bugs reach production: the happy path never triggers them.

Cause 2: encoding the whole URL instead of its parts

Percent-encoding operates on values, not on URLs. A URL is a structure whose delimiters (:, /, ?, &, =, #) carry meaning. Running an entire assembled URL through encodeURIComponent escapes those delimiters along with everything else, so https://example.com/a b?q=1 becomes a single opaque token beginning https%3A%2F%2F. Any component of it that was already encoded now shows a doubled escape.

// Wrong: escapes the URL's own structure
const bad = encodeURIComponent(`/search?q=${term}`);

// Right: encode each value once, as the URL is built
const url = new URL("/search", "https://example.com");
url.searchParams.set("q", term);   // encodes term exactly once

The URL and URLSearchParams APIs exist to make this hard to get wrong. They take raw values and encode on serialization. Hand them an already-encoded value and you get %2520 right back, so the discipline is to keep raw values raw until the moment of assembly.

Cause 3: a value that legitimately needs two layers

Not every double encoding is a bug. When a URL is carried inside another URL, as with an OAuth redirect_uri or a return_to parameter, the inner URL is a value of the outer one and must be encoded to survive transit. Its own / characters become %2F. If that inner URL itself contained an encoded parameter, that parameter is now at two layers, correctly, and %252F is exactly what should appear on the wire.

The rule that keeps this straight is that each boundary decodes exactly once, and only the layer it owns. The outer service decodes the parameter to recover the inner URL; the inner service decodes that URL's own parameters. Trouble starts when a middlebox decodes greedily and keeps going, or when a handler decodes the value it was given and then passes it on without re-encoding.

Cause 4: proxies, gateways, and framework middleware

A request can pass through a CDN, a reverse proxy, a load balancer, and a framework router before your handler sees it. Each of these may normalize the path. Most decode once and forward the decoded form; a few forward the original and decode again downstream. Two components that each assume they own the decoding step will either double-decode, which is a security concern because it can smuggle an encoded ../ past a path check, or double-encode, which produces the %2520 you are looking at.

Diagnose this by comparing the path at each hop, ideally by logging the raw request target at the edge and again in the application. Once you know which hop introduced the extra layer, disable normalization there rather than compensating with an extra decode downstream. A compensating decode looks like a fix and quietly breaks any value that legitimately contains a percent sign, such as a discount code reading SAVE20%.

Cause 5: encoded values persisted to storage

If %2520 appears in your database, an encoded value was written instead of the raw one, and the render path encoded it a second time on the way out. This is the most expensive variant because the data itself is now wrong and a code fix alone will not repair existing rows. Decode on ingest, store raw text, encode only at output, and backfill the affected rows with a one-pass decode that you have first tested against values containing real percent signs.

Diagnose it in one step

You do not need to reason about the chain to identify the layer count. Open the URL Encoder and Decoder, paste the URL or the single parameter value, and decode. If the output still shows percent escapes standing in for ordinary characters, decode again and count the passes. Two passes to reach clean text means one encoder too many, and the table above tells you which cause to hunt for. Because it runs entirely in your browser using the native encodeURIComponent and decodeURIComponent APIs, a URL carrying a session token or a signed redirect never leaves your device.

If the value turns out to be HTML-escaped rather than percent-encoded, with artefacts like & instead of %2520, the HTML Entity Encoder and Decoder is the right tool, and HTML entities vs URL encoding explains which escaping belongs where. Both mistakes come from the same habit of escaping a value without knowing what state it is already in. Browse the Tools Nimbus guides for more developer fixes.

How to prevent it

  • Encode exactly once, at the moment you build the URL. Treat encoding as a serialization step, not something a value carries around with it.
  • Store and pass raw values internally. A variable holding hello%20world is a bug waiting for the next function that encodes it.
  • Use URL and URLSearchParams(or your language's equivalent) rather than string concatenation, and give them raw input.
  • Never encode an assembled URL. Encode path segments and parameter values individually.
  • Decode once per boundary you own, and never add a compensating decode to paper over an upstream double encode. It breaks legitimate percent signs.
  • Test with a value containing a space, an ampersand, and a literal %. Unreserved-only test data hides every one of these bugs. For the wider picture of which escaping applies to which context, see the best free online URL encoder and decoder.

Frequently asked questions

What does %2520 mean in a URL?+

It means a space was percent-encoded twice. A space encodes to %20. If that already-encoded string is encoded again, the percent sign itself becomes %25, so %20 turns into %2520. The URL is not corrupted and no data is lost; it simply carries one more layer of encoding than the server expects. Decoding it once yields %20, and decoding that yields the original space.

How do I fix a double-encoded URL?+

Decode it once for each layer of encoding, then fix the code that added the extra layer. Decoding %2520 once gives %20, which is the correctly encoded form you want in a URL. The permanent fix is to encode exactly once, at the moment you build the URL, and to store raw unencoded values everywhere else. Re-encoding a value that arrived already encoded is what creates the second layer.

Why did %20 become %2520 when I encoded my URL?+

Because you encoded a string that already contained percent-encoded characters. Percent-encoding escapes the percent sign itself as %25, since a bare % is a reserved character that introduces an escape sequence. So encoding the three characters % 2 0 produces %25 followed by the literal 2 and 0, which reads as %2520. The encoder behaved correctly; it was given input that was already encoded.

Should I encode the whole URL or just the query parameter values?+

Encode only the individual parameter values and path segments, never the assembled URL. Encoding a whole URL escapes its structural characters, so :// becomes %3A%2F%2F and the ? and & separators become %3F and %26, which destroys the URL as a URL. Build the URL from raw parts and let your URL or query-string library encode each value once as it assembles them.

Is it safe to paste a URL with tokens into an online decoder?+

It depends on whether the tool sends your input to a server. Tools Nimbus decodes URLs entirely in your browser using the native encodeURIComponent and decodeURIComponent APIs, so a URL containing a session token, a signed redirect, or an API key never leaves your device. Avoid any decoder that round-trips your data through a backend when the URL contains anything sensitive.

How many times was my URL encoded?+

Count the layers by decoding repeatedly until the output stops changing. Each extra encoding pass inserts another 25 after the percent sign, so a space reads as %20 at one layer, %2520 at two, and %252520 at three. Watching one 25 disappear per decode is the quickest way to count the layers, and the count usually tells you how many systems in the chain are re-encoding the value.

Try these browser-based tools mentioned in this guide. Everything runs locally, so your data never leaves your device.