Tag: Redirects

  • Resolving Unexpected WordPress Redirects

    I recently had an issue on a WordPress site, where no matter where I looked, I couldn’t find the source of a 301 redirect. After a lot of frustration, I finally determine the cause and came up with a solution.

    How it began

    There was a simple category which had 4 sub-categories underneath it. Each was unique, and the posts under each category were accessible without any issues. For 2 of the sub-categories, accessing the category URL’s showed the list of the entries in those categories properly. However, for 2 of the sub-categories — which each only had a single post — it would consistently redirect to the post rather than show the list of posts in those subcategories when their URL’s were loaded. Since I had made several adjustments to both the category structure and slugs, as well as the post slugs, it appear a 301 redirect had been created by WordPress as a result.

    The Obvious Culprits

    First, it goes without saying that it’s important to eliminate the most obvious potential causes for a redirect…

    • WordPress Settings — It’s always a good idea to double check all your settings. In this case, I made sure the categories and posts were named as needed with the expected slug values set. In addition, I checked the Permalink settings and all was exactly as I wanted. Some suggestions I found were to change the Permalink settings temporarily, and then change them back to force WordPress to “fix” any mistakes. YMMV, but this did not help.
    • Browser Caching — Great for performance, but often frustrating when developing and debugging a site. Opening Chrome’s DevTools and ensuring the “Disable cache” option is st on the Network tab is enough to eliminate this as a cause. If you need further information on redirects, the Link Redirect Trace Chrome plugin is great and will let you know if any redirect is coming from a server response or the browser’s cache, and a lot more.
    • WordPress or Server-Side Caching — I had no caching setup within WordPress, nor at the server for this site yet.
    • .htaccess — You can easily forget any redirects you might have added to the .htaccess file — even the Apache or Ngnix config — but this was not the case here.
    • Redirection Plugin — This is a fantastic plug which I highly recommend, and entries added automatically when slugs are changed definitely could have been the reason for the redirect. This was actually the first thing I checked, but I had removed any entries that could get in the way. Most useful though is the Redirect Tester in the Support page of the plugin, which verified the redirect was coming from WordPress itself.

    This is not an exhaustive list, as there’s always the potential for other code to be creating redirects. It could be coming from another plugin or the theme, or even a conflict between plugins or between a plugin and the theme.

    The Database

    Since there was nothing obvious in the WordPress admin console that could have been causing the redirect, and I knew the redirect was coming from WordPress itself, this suggested there could be something incorrect in one of the database tables. I suspected this was the cause and likely resulted from the various changes I made to the category and post slugs while I tweaked them multiple times to create the right URL structure for the site.

    After some testing with and without posts in the categories which were redirecting, and even after completely renaming or deleting the posts, I discovered the redirects were only happening when the posts were present. Without them, the categories displayed correctly (although without any items). It seemed as-if something was incorrect for these posts in the db.

    Unfortunately, the WordPress routing process is fairly complex, as is its denormalized database schema. It can very be challenging to locate potential issues of this nature in the tables. That said, WordPress does maintain the previous slug value as meta data for each post when you change the slug. This is stored as an _wp_old_slug entry in the meta data for the post. You can see these in the db as follows:

    select * from wp_postmeta where meta_key = '_wp_old_slug';

    Since this was a new site, and I knew there was no need for any of these old slug values, I removed them. If you do so, please sure you understand any implications this could cause as this cannot be undone (unless you’ve created backups).

    delete from wp_postmeta where meta_key = '_wp_old_slug';

    Unfortunately, this still did not solve the issue.

    More Digging Into the Database and Online

    With a bit more digging into the database coupled with related online research into values found, for a moment I thought I had found another meta values which could be involved — _wp_disired_post_slug. However, it was unrelated and not something to be changed or removed.

    I did discover though that tracking the source of such unexpected redirects was an issue many have had over the years. Some had luck with items I listed above, while others seemed to be in the same boat as I was in. And then, I realized the cause was something much, much simpler, in fact, so simple that I had foolishly, completely overlooked it.

    Guessing the URL

    During WordPress’s routing process, it will attempt to “guess” at a URL in order to help resolve issues when the location of a post (i.e. it’s category or parent) has changed, or if the post’s slug itself has been changed. This is a great feature to help avoid 404’s when things are renamed on an active site, but it definitely can get in the way. In fact, for 9 years it seems this was an issue with an open ticket that was eventually addressed in new features to give us control here.

    The following filters allow fine-grained control over how WordPress will handle such routing guesses:

    do_redirect_guess_404_permalink

    If this filter is added, and set to return false, it will completely disable any redirect guessing. Sure enough, once I did this, the problem was resolved. In hindsight, this should have been my first thought, as the categories and articles with the issues were in fact named similarly enough to result in this issue.

    Adding this with Code Snippets (another highly useful plugin) was how I first tested this out:

    add_filter('do_redirect_guess_404_permalink', '__return_false');
    strict_redirect_guess_404_permalink

    This filter allows you to maintain the URL guessing, but control what type of comparison will be used by WordPress to make the guess. When this filter returns true, strict comparisons will be done and only exact matches to the post slug will be used. When the filter returns false, loose comparisons are done and it will match much more. By default, loose comparisons are used.

    As far as how these actual matches are made, looking at the code reveals the SQL queries are effectively as follows [Note: this is not the actual code, but a simplification to clarify the end result]…

    • Strict Comparison:
      where post_name = "$name"
    • Loose Comparison:
      where post_name like "${name}%"

    As you can see, a strict comparison will only match posts where the slug exactly matches what is in the URL, which should find posts when their parent has changed, but not if the post slug itself has changed. However, a loose comparison will match when the slug starts with the same value from the URL. While this may be useful in some cases where an post slug changes slightly, I can certainly see many situations where it can cause issues. In my case it was exactly why it was doing the redirects I did not want done.

    pre_redirect_guess_404_permalink

    This filter provides yet another option, as it allows for any custom logic to determine where a redirect should go. If this function is used, it can return a string to indicate the URL as the redirect destination. Alternatively, it can return false to bypass the redirect guessing (i.e. same as if do_redirect_guess_404_permalink returned true, or if it returns null the redirect guessing continues as normal.

    Conclusion

    After determining the cause, I considered using strict comparisons as a compromise between completely disabling these redirect guesses, and allowing them to match only based on the start of a post slug. Since I am using the Redirection Plugin, and have carefully setup any redirects I want to use, I decided any incorrectly entered routes should return a 404. If I do change any categories, I’ll be sure to add redirects as needed, but this puts me in total control of which routes redirect and which do not.

    In the future, I may put together a small plugin to expose control for these behaviors easily in the Settings for any site, perhaps with more advanced logic which will determine which parts of the site allow for strict, loose or no redirect guessing.

    References