Make WordPress Core

Opened 5 weeks ago

Last modified 7 hours ago

#64376 new defect (bug)

redirect_canonical() causes unnecessary 301 redirects for query string encoding variants (+ vs %20)

Reported by: robbertvancaem's profile robbertvancaem Owned by:
Milestone: 7.0 Priority: normal
Severity: normal Version:
Component: Canonical Keywords: has-patch has-unit-tests
Focuses: performance Cc:

Description

Summary

redirect_canonical() issues 301 redirects when query strings contain + characters, redirecting them to %20 encoded equivalents. Since both are functionally identical in query strings (RFC 3986), these redirects serve no purpose and
cause real-world problems.

Environment

  • WordPress version: 6.8.3
  • PHP version: 8.3
  • Hosting: Kinsta (with page caching)
  • Relevant setting: Static front page enabled

Steps to Reproduce

  1. Set up a WordPress site with a static front page
  2. Visit: https://example.com/?utm_content=Hello+World
  3. WordPress redirects 301 to: https://example.com/?utm_content=Hello%20World

Root Cause

In wp-includes/canonical.php (lines 576-594), query strings are rebuilt using rawurlencode_deep(), which converts + to %20. The function then compares the rebuilt URL to the original and issues a redirect, even though both URLs
resolve to identical content.

Real-World Impact

This caused a severe production incident on our site:

  • Marketing campaign URLs with + in UTM parameters triggered 301 redirects
  • Our page cache stored the 301 response
  • All subsequent visitors received cached 301 redirects, creating redirect loops
  • Campaign success rate dropped from 100% to 2% over several days
  • Affected thousands of users before root cause was identified

This affects any site using:

  • Page caching (Kinsta, WP Super Cache, W3 Total Cache, etc.)
  • Marketing campaigns with spaces in UTM parameters
  • Any query strings containing + characters

Proposed Fix

Before returning a redirect, compare decoded URLs:

// In redirect_canonical(), before the final redirect:
if ( $redirect_url && urldecode( $redirect_url ) === urldecode( $requested_url ) ) {
    return false;
}

This preserves all meaningful redirects (www normalization, trailing slashes, pretty permalinks) while preventing encoding-only redirects that provide no SEO or functional benefit.

Current Workaround

add_filter( 'redirect_canonical', function( $redirect_url, $requested_url ) {
    if ( $redirect_url && urldecode( $redirect_url ) === urldecode( $requested_url ) ) {
        return false;
    }
    return $redirect_url;
}, 10, 2 );

Why This Should Be Fixed

  1. No benefit: Both + and %20 represent spaces in query strings; redirecting between them serves no SEO or functional purpose
  2. Breaks caching: 301 responses get cached, causing redirect loops
  3. Breaks analytics: Redirect strips original referrer data
  4. Violates user expectations: UTM parameters are designed for variation; users don't expect redirects based on encoding

Attachments (1)

64376.diff (1.7 KB) - added by iflairwebtechnologies 5 weeks ago.
canonical: avoid unnecessary redirects for encoding-only differences (fix #64376)

Download all attachments as: .zip

Change History (7)

@iflairwebtechnologies
5 weeks ago

canonical: avoid unnecessary redirects for encoding-only differences (fix #64376)

#1 @iflairwebtechnologies
5 weeks ago

  • Keywords has-patch added

When redirect_canonical() rebuilt URLs, it sometimes triggered 301 redirects
only because the query string used an encoded variant (e.g. " " vs "%20" or "+").
This change compares decoded URLs and suppresses redirects when decoded forms
are identical, avoiding harmful encoding-only 301s that cause caching issues
and break tracking parameters. See Trac #64376.

#2 @westonruter
5 weeks ago

  • Focuses performance added
  • Milestone changed from Awaiting Review to 7.0

I was able to reproduce the issue when accessing a static homepage.

I was not able to reproduce the issue when appending ?utm_content=Hello+World to a post permalink, however.

Tagging this as a performance defect because the redirect needlessly slows down page loads.

#3 @westonruter
5 weeks ago

  • Keywords needs-patch added; has-patch removed

@iflairwebtechnologies The patch in 64376.diff does not apply. I also do not see how it would fix the problem, since it is specifically targeting attachment URLs when the issue is specifically for the static front page. This is not the first time I've seen a patch supplied like this from you. See previous example. Please do not waste other contributors' time by submitting patches that clearly do not apply and which have not been tested.

#4 @westonruter
5 weeks ago

  • Version 6.8.3 deleted

#5 @iflairwebtechnologies
5 weeks ago

@westonruter

Thank you for your feedback.

We appreciate you pointing out the issues with the patch. We apologize for the oversight, we will ensure that all future patches are properly reviewed, tested, and correctly aligned with the reported issue before submission.

This ticket was mentioned in PR #10724 on WordPress/wordpress-develop by @sanket.parmar.


7 hours ago
#6

  • Keywords has-patch has-unit-tests added; needs-patch removed

This PR fixes unnecessary 301 redirect triggered by redirect_canonical() when a query string contains a + (plus sign) instead of %20.

While both + and %20 are valid representations of a space in a query string, WordPress's redirect_canonical() logic uses rawurlencode_deep(), which strictly follows RFC 3986 (using %20). When comparing the requested URL to the canonical candidate, the mismatch in encoding triggers a redirect. This can lead to redirect loops, increased server load, and cache poisoning in environments with edge caching.

Changes

  • Updated redirect_canonical() in wp-includes/canonical.php to normalize query string encoding before comparison.
  • Implemented a check that treats + and %20 as equivalent within the query string portion of the URL.
  • Ensures that if the only difference between the current URL and the redirect candidate is the space encoding, the redirect is suppressed.

Testing Instructions

  • Set up a WordPress site with pretty permalinks enabled.
  • Navigate to a URL with a plus sign in a query parameter, for example: example.com/?s=hello+world.
  • Before Patch: Observe a 301 redirect to example.com/?s=hello%20world.
  • After Patch: The page should load directly without a 301 redirect.
  • Verify that search results and other query-dependent pages still function correctly.

Trac ticket

Note: See TracTickets for help on using tickets.