Blog

  • 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

  • Useful Font Awesome Icons

    This is a work in progress, but below are some useful icons in the classic Font Awesome set.

    Computer / Technology

    cloud
    server
    database
    desktop
    laptop
    tablet
    mobile-phone
    code
    microchip
    terminal
    keyboard-o
    sitemap
    hashtag
    qrcode
    html5
    css3

    Security

    shield
    lock
    unlock
    unlock-alt
    key

    Actions

    send
    print
    filter
    send
    send-o
    remove
    trash
    trash-o
    upload
    download
    cloud-upload
    cloud-download

    Tools / Parts / Things

    wrench
    gear
    gears
    flask
    thumb-tack
    lightbulb-o
    plug
    live-saver
    fire-extinguisher
    coffee
    glass

    Weather

    sun-o
    moon
    umbrella
    snowflake-o
    flash

    Symbols

    warning
    info-circle
    recycle
    crosshairs

    Analytics

    bar-chart
    line-chart
    pie-chart
    area-chart

    Users

    user
    users
    user-secret
    id-card
    id-card-o
    address-card
    address-card-o

    Contact

    phone
    envelope
    envelope-open
    envelope-o
    envelope-open-o
    fax
    phone-square
    envelope-square

    Business / Legal

    building
    building-o
    bank
    briefcase
    balance-scale
    gavel

    Vehicles

    car
    taxi
    truck
    bus
    train
    ship
    plane
    fighter-jet
    rocket

    Map / Navigation

    globe
    globe-w
    globe-e
    road
    map
    map-o
    map-signs
    map-marker
    map-pin

    Shopping / Commerce

    barcode
    shopping-cart
    money
    dollar
    credit-card
    credit-card-alt
    cc-visa
    cc-mastercard
    cc-amex
    cc-discover
    cc-paypal
    cc-stripe

    Shapes

    cube
    cubes
    circle-o
    circle-o-notch
    circle-thin
  • Setting Up Global Git Ignore Rules

    To setup a file with a list of items which should be ignored globally on your local system:

    git config --global core.excludesfile ~/.gitignore_global

    To view the currently set file:

    git config --get core.excludesfile

    My current file for use on macOS:

    # General files
    .DS_Store
    .AppleDouble
    .Apple Double
    .LSOverride
    
    # Thumbnail cache files
    ._*
    Thumbs.db
    
    # Custom icon file (which ends with \r), but not other icon* entries
    Icon?
    ![iI]con[_a-zA-Z0-9]
    
    # Files which can be in root of an external volume
    .com.apple.timemachine.donotpresent
    .DocumentRevisions-V100
    .fseventsd
    .Spotlight-V100
    .TemporaryItems
    .Trashes
    .VolumeIcon.icns
    
    # Possible directories on a remote AFP share
    .apdisk
    .AppleDB
    .AppleDesktop
    .Apple Desktop
    Network Trash Folder
    Temporary Items
    
    # Application specific files/directories
    .idea/
    .vscode
    *.code-workspace
    
    # Local WIP and Notes
    /_*.md
    /_ToDo
    /_Old
    /_Open
    /_WIP
    
    # Temporary test files
    /t
    /t.*
    /t-*
    /t[0-9]
    /t[0-9].*