Troubleshooting

Fixing URL-encoding bugs.

The ten most common symptoms of broken URL encoding, what causes each, and the practical fix. From %2520 double-encoding and duplicate-content crawl traps to garbled non-English characters.

Ten symptoms that mean URL encoding has gone wrong, what causes each, and how to fix it.

1. The URL contains literal %20 instead of spaces

Symptom: Users see https://example.com/My%20Document in their address bar where they expected https://example.com/My Document.

Cause: This is normal. Browsers display the encoded form of the URL but render decoded text where they can. %20 in the address bar is correct and expected for spaces in paths.

Fix: No fix needed. The URL is working correctly. If it bothers you visually, you can copy and decode it for readability — but the literal form is the canonical, transmittable form.

2. The URL contains %2520 instead of %20

Symptom: The receiving system sees a literal %20 as if it were data, not a space.

Cause: Double-encoding. The string was encoded twice. The percent sign % in %20 got encoded as %25 on the second pass, giving %2520.

How it happens:

Fix: Find the layer doing the second encoding and remove it. Two strategies:

If you can’t change the upstream code, decode the input once before processing: decodeURIComponent(input).

If our tool gets a double-encoded string, enable “Decode recursively” on the decoder. It’ll keep decoding until no %XX remain — up to 16 rounds.

3. Decoded text shows garbled characters like café, café, or caf%E9

Symptom: Special characters render as accented Latin gibberish or get replaced with question marks.

Cause: Character-set mismatch. The original bytes were encoded with one charset, and you’re decoding with another.

Specific diagnoses:

Fix: Change the destination character set on the decoder. Try UTF-8 first (the default — most modern data). If it’s legacy Western European data, try Windows-1252 or ISO-8859-1. For old Russian, try Windows-1251 or KOI8-R. For old Japanese, Shift_JIS or EUC-JP. For old Chinese, GBK or Big5.

4. Parameter values are getting cut off at the ampersand or hash

Symptom: You send ?q=rock & roll&lang=en, the server reads q=rock and a separate parameter roll that wasn’t in your data.

Cause: Reserved characters in the value weren’t encoded. The & in your data is being interpreted as a parameter separator. Same problem with literal # ending the query and starting a fragment.

Fix: Encode each parameter value with encodeURIComponent (or equivalent in your language) before assembling the URL:

// Wrong
const url = `?q=${userInput}&lang=en`;

// Right
const url = `?q=${encodeURIComponent(userInput)}&lang=en`;

5. Plus signs in input become spaces in output (or vice versa)

Symptom: A search for C++ programming returns results as if the user searched for C programming.

Cause: Confusion between the two URL-encoding conventions for spaces.

Browsers use form encoding when submitting HTML forms via GET. Most server frameworks expect form encoding in query strings. Path components use RFC 3986 strict.

Fix: Decide which convention applies to your context, then either:

On the encoder side: use the right function. JavaScript’s encodeURIComponent produces RFC 3986 (literal +). URLSearchParams produces form encoding (+ for space).

On the decoder side: toggle the “Treat + as space” option. On for form-encoded data (the default), off for path components.

6. Long URLs get truncated

Symptom: The URL works locally but fails in production with errors like “414 Request-URI Too Large” or just silent truncation.

Cause: Browser, proxy, or server URL-length limits. Practical limits vary:

Fix: If your URL exceeds 2,000 characters, restructure. Use POST instead of GET for large payloads. Send big data in the request body, not the URL. For sharing long URLs, use a URL shortener.

7. The URL works in Chrome but breaks in Safari (or vice versa)

Symptom: URL behaviour differs between browsers.

Cause: Browsers normalize URLs slightly differently. Common cases:

Fix: Test in all browsers. If a path needs to contain a literal / as data, change the structure (encode it as a query parameter instead). If you’re hitting the encoded-slash-in-path restriction, configure the server to allow it (Apache: AllowEncodedSlashes On; Nginx: special handling needed).

8. The same URL is indexed twice as %C3%A9 and %c3%a9

Symptom: Search Console reports duplicate URLs that look identical, differing only in the case of the hex digits — /caf%C3%A9 and /caf%c3%a9. Analytics splits pageviews across both. Crawl budget is being spent twice on one page.

Cause: RFC 3986 says the two hex digits after a % are case-insensitive, so %C3%A9 and %c3%a9 refer to the same byte. But that’s a rule for comparison, not for storage. Many web servers, routing layers, and analytics tools treat the raw path string as an opaque key and never normalize the case, so the two forms become distinct entries. When one platform shares the uppercase form and another auto-lowercases it, crawlers see two URLs for one resource.

Fix: Pick one canonical case and enforce it consistently. RFC 3986 recommends producers emit uppercase hex, so that’s the safest target, but the more important thing is that every layer agrees. Two complementary defenses:

To check whether two URLs are truly equivalent, decode both in our URL decoder — if they produce the same text, they’re the same resource and should collapse to one canonical form.

9. A query parameter balloons into %25253A on every click

Symptom: A faceted filter (product category, dashboard controls) works on the first click, then the parameter grows each time: color%3Abluecolor%253Abluecolor%25253Ablue. Eventually the request fails with 431 Request Header Fields Too Large or a similar limit error.

Cause: This is double-encoding (see #2) happening in a loop. The UI captures the current, already-encoded query string and re-encodes the whole thing on each interaction. Every pass turns each % into %25, so %3A becomes %253A, then %25253A, and so on until the URL blows past the server’s header-size limit.

Fix: Never encode a string that’s already part of the URL. Keep filter state as plain values in memory (an object or array), and rebuild the query string from scratch on each change instead of mutating the live URL:

// Wrong — re-encodes the already-encoded URL each time
url = encodeURIComponent(location.search + newFilter);

// Right — build fresh from unencoded state
const params = new URLSearchParams();
for (const [key, value] of Object.entries(state)) {
  params.set(key, value); // URLSearchParams encodes once
}
history.replaceState(null, '', '?' + params.toString());

If you’ve already got a runaway value, our decoder with “Decode recursively” will peel off all the layers so you can see the original value.

10. Encoding the whole URL breaks https:// into https%3A%2F%2F

Symptom: A request fails immediately with a malformed-URL or pathing error, and the “URL” being sent looks like https%3A%2F%2Fexample.com%2Fpath.

Cause: A blanket encode was applied to a fully assembled URL — encodeURIComponent(fullUrl) in JavaScript, urllib.parse.quote(full_url) in Python — usually “to be safe.” But encodeURIComponent is designed to encode a single value, so it escapes the structural characters too: the : and / that separate the scheme, host, and path. The result is one long opaque string that no client can parse into a real request.

Fix: Encode the parts, not the whole. Only individual dynamic values — a query value, a single path segment — should be percent-encoded, and only after you’ve decided where they go:

// Wrong — destroys the URL structure
const url = encodeURIComponent(`https://example.com/search?q=${term}`);

// Right — encode only the dynamic value
const url = `https://example.com/search?q=${encodeURIComponent(term)}`;

When you need the whole URL wrapped as a value — passing it as a redirect_uri, for example — that’s a legitimate use of encodeURIComponent, but you encode it as the value of a parameter in an outer URL, never as the request target itself. Our URL builder assembles URLs part-by-part so the structure stays intact and only the values get encoded.

How to debug URL encoding

When something doesn’t look right:

1. Look at the raw URL. Open your browser’s DevTools → Network tab → reload. The actual URL that gets sent is in the request line. Compare with what you intended.

2. Decode it. Paste the broken URL into our URL decoder. If the decoded form has extra % signs, you have double-encoding. If the decoded form looks garbled, you have a charset mismatch.

3. Parse the components. Use our URL parser to see how the browser/server splits the URL into protocol, host, path, query, and fragment. The component that’s wrong is where your bug is.

4. Test with a known-good value. Replace your dynamic input with a hardcoded literal that has no special characters. If the URL works, encoding is the bug. If it still fails, the bug is elsewhere.

Common questions

About this topic.

Double-encoding. Your string was URL-encoded twice. The % in %20 got encoded as %25 on the second pass, giving %2520. Find the upstream code that's applying a second round of encoding, or use the "Decode recursively" option on our decoder to peel off both layers automatically.

Character-set mismatch. The bytes were encoded in a different charset than you're decoding with. Switch the destination charset selector — try Windows-1252 for old Western European data, Shift_JIS for old Japanese, GBK for Simplified Chinese, KOI8-R for old Russian. UTF-8 is the modern default.

The ampersand in your data wasn't encoded. & is a reserved character that separates query parameters; an unencoded & inside a value gets interpreted as the start of a new parameter. Fix: pass each value through encodeURIComponent before assembling the URL.

Two URL-encoding conventions exist. In form-encoded query strings (what HTML form GET produces), + means space. In RFC 3986 paths, + is a literal plus. The decoder's "Treat + as space" option lets you pick — turn it on for query strings, off for path components.

There is no universal limit, but practical limits exist: Apache caps at 8,190 bytes by default, Nginx at 4,096, some CDNs lower. Older browsers had ~2,000-character limits. For payloads larger than this, use POST with a request body instead of GET with query parameters.

Yes — RFC 3986 says percent-encoding hex digits are case-insensitive, so both decode to the same byte and refer to the same resource. The problem is that many servers and analytics tools treat the raw strings as distinct keys, so both can get indexed as separate URLs. Pick one case (uppercase is the RFC recommendation), enforce it at your CDN or proxy, and set a canonical tag so crawlers only count one.

Your UI is re-encoding the whole query string on every click, so each % turns into %25 on each pass. Don't encode a string that's already in the URL — keep filter state as plain values in memory and rebuild the query string from scratch with URLSearchParams on each change. To recover a runaway value, use "Decode recursively" on our decoder.

No. encodeURIComponent encodes a single value, so it escapes the : and / that separate the scheme, host, and path — turning https:// into https%3A%2F%2F and breaking the request. Encode only individual dynamic values (a query value or one path segment), then assemble the URL. Encoding a whole URL is only correct when it's the value of a parameter in an outer URL, like a redirect_uri.

Sources & standards

The fixes in this guide follow the primary web standards that define URL encoding. The relevant specifications:

Honest scope: server URL-length limits (section 6) and the 431 header-size behaviour (section 9) aren’t set by these standards — they’re per-server configuration defaults (Apache, Nginx, and CDN settings), which is why the numbers vary by environment rather than being fixed by any spec.

Related

Try the tool.

About this page

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.