Web Redirects and the Law of Specificity

A common mistake when setting up multiple redirects for a website is failing to put them in the correct order. Typically, the person setting them up realizes that order is important; but it isn’t always clear how the redirects should be ordered.

Let’s take a look at a simple use case where there are multiple redirects:

Redirect 301 /contact-us /contact
Redirect 301 /our-team /about
Redirect 301 /our-team/john-doe /john-doe

The first redirect can actually live anywhere in the file since there is no competing rule.

However, the other two redirects are competing rules. You can easily find competing rules by finding any redirects where the ‘from’ portion of the path is the same.

In our example, the second redirect will pick up a request for /our-team/john-doe and send it to /about/john-doe. Since a redirect triggers immediately once a matching rule is found, our last redirect is never hit.

The Law of Specificity

The Law of Specificity states:

Web redirects should always be ordered by specificity. The most specific redirect rules should always come first and the more general rules should come last.

Let’s reorder our previous example according to this new insight:

Redirect 301 /contact-us /contact

Redirect 301 /our-team/john-doe /john-doe
Redirect 301 /our-team /about

As you can see, now that we’ve placed the more specific rule first, the /our-team/john-doe request will get forwarded to /john-doe as expected and any other requests to the /our-team path will be appropriately redirected within the /about path.

On a side note, I’ve decided to separate all of my non-competing rules from my competing rules. For competing rules, I like to group them based on their shared ‘from’ path.

WWW Redirects with .htaccess

Determining whether to use www in your canonical URLs is an important decision and should be reinforced by redirects to prevent duplicate content from an SEO perspective.

Definition of a WWW Redirect

A www redirect is a rule on your web server that forwards all traffic from the non-www version of your domain to the www version, or vice versa.  

For example, lets say your web address is www.example.com and someone types in example.com into their browser.  The browser will send them to example.com.  When that request hits your web server, the redirect will tell the browser that the correct web address is actually www.example.com and will send the user there instead.  You can also setup redirects that work the other way around and send users from www.example.com to example.com.

If you are trying to decide whether to use a naked domain or a www subdomain in your canonical URLs, be sure to read the article “Why use WW?“.

The HTTP protocol used on the web has a numerical system for identifying the status of page requests.  The 301 status code indicates that the page or resource requested has ‘moved permanently’.  When implementing your www redirect it is important that you return a 301 status code so that search engines are clear which URL should be indexed.

Avoiding Duplicate Content with Redirects

Every website has what is called the ‘site root’.  That is the main directory on your web host that is publicly available when a user goes to your domain.  By default, this directory can be accessed by typing in the root domain one of two different ways www.example.com or example.com.  This means that for every page on your website, it can be accessed via two different URLs: the www version and the non-www version.  When search engines, such as Google, come along to crawl and index your site, they may come to the same page via the two different versions of the URL.  When a search engine finds the same content at different URLs, it is called duplicate content.

Duplicate content is an issue because it puts your own site in competition with itself.  A page that might normally have great rankings in search now has another copy of itself in the search index.  When a user types in a keyword, how does the search engine know which page to display? Is one copy better than the other?  To find the answer, search engines will look at off-page factors such as the number of incoming links.  If you have links pointing to just one of the pages, then that one will probably be selected. If not, then that means you have users linking to two different URLs for the same content.  Imagine how much better you would rank in search if all those links were for the same page.  Allowing the same content to be accessed via multiple URLs dilutes your SEO efforts and devalues your content in the eyes of search engines.  The fix is to simply redirect everyone, search engines included, to the desired URL using 301 redirects.

Locating and working with your .htaccess file

If you aren’t using an Apache server, then what I am about to tell you won’t work.  If you are, then you will need to find or create an .htaccess file at your site’s root directory.  

The .htaccess file may, or may not, be visible when viewing your site’s file structure through cPanel or your favorite FTP editor.  If you don’t see an .htaccess file, look around for an option that will allow you to see hidden files.  If you still don’t see it, then go ahead and create a new file named .htaccess.  If you have to create one, you may have to upload an empty text file and rename it to .htaccess (not .htaccess.txt).  Once you have found or created your .htaccess file, then you just need to place your redirect rules within it.

If your .htaccess file already has stuff in it, make a copy and save it somewhere in case you have to revert back! There is nothing worse than breaking your entire site because you messed up an important file and didn’t save a backup.

Implementing the WWW Redirect

First, make sure that mod_rewrite is enabled.  Just make sure that this line appears somewhere in your .htaccess file above the rule that we are about to add:

RewriteEngine On

To redirect from non-www to the www version of your site, use the following rule:

RewriteCond %{HTTP_HOST} ^example.com [NC]
RewriteRule (.*)$ http://www.example.com/$1 [R=301,L]

To redirect from www to the non-www version of your site, use the following rule:

RewriteCond %{HTTP_HOST} ^www.example.com [NC]
RewriteRule (.*)$ http://example.com/$1 [R=301,L]

Make sure that you replace example.com with your real domain!

Simple Redirects with .htaccess

Anytime you migrate a web site, or even just move a single page to a new URL, redirects make sure that your users don’t get lost in the shuffle. Search engines also use redirects to aid in properly indexing your site’s content.

It is important to understand the distinction between the two most common types of redirects:

  • Permanent redirect — A permanent redirect, or 301 redirect, should be used anytime you permanently move a page, directory or website.
  • Temporary redirect — A temporary redirect, or 302 redirect, should be used if you want to temporarily point a user to another location.

If you are running an Apache server, then you can create a file named .htaccess in any directory to be able to locally override certain server configurations. It is very common for an .htaccess file to exist at the root directory of a website. You can use a very simple syntax within an .htaccess file to setup page, directory and site-wide redirects. While you can get quite advanced with URL redirects, we are going to get started with the simplest use cases.

There are three common use cases when setting up redirects:

  • Redirect a single page to a new page
  • Redirect a whole directory to a new directory
  • Redirect an entire site to a new site

Redirect Syntax

This is the basic syntax for redirects written using the mod_alias redirect directive in Apache:

Redirect [status] URL-path URL
  • Make sure you capitalize the R in Redirect or it won’t work. Everything is case sensitive.
  • The status is optional and is usually a number indicating the HTTP status code you want to deliver to the browser. You can use the word permanent in the place of 301, or temp in the place of 302. If not provided, then 302 will be used as the default.
  • The URL-path is required and is always a path relative to the site root, not the location of the .htaccess file.
  • The URL is required and is either a path relative to the site root, assuming the redirect is within the same site, or an absolute URL if the redirect points to another site.

Redirect a Single Page

Let’s start with a simple redirect where you want to point one page to another page:

Redirect 301 "/old-page.html" "/new-page.html"

As you can see, we are doing a 301 (permanent) redirect from a page on the current site, to another page on the same site.

If the new page is located at another domain, or even subdomain, then here is how you would write the redirect:

Redirect 301 "/old-page.html" "http://www.new.com/new-page.html"

It is perfectly acceptable to use this method even if the page is on the same site. It never hurts to be more explicit and use an absolute URL.

Consider how the last example plays out with a few different URL variations:

# Target URL Destination URL
1 http://old.com/old-page.html http://www.new.com/new-page.html
2 http://old.com/old-page.html?q=21&id=902 http://www.new.com/new-page.html?q=21&id=902
3 http://new.com/old-page.html http://www.new.com/new-page.html
4 http://www.new.com/old-page.html http://www.new.com/new-page.html


  • Redirect #1 — Takes a page on an old domain to a new page on a new domain.
  • Redirect #2 — Does the same thing as the first redirect, but demonstrates that any URL GET parameters are passed along as part of the redirect.
  • Redirect #3 — An example of redirecting from the current domain to a subdomain.
  • Redirect #4 — Demonstrates a redirect that takes place on the same (sub)domain.

Redirect a Whole Directory

Here is an example of how you would redirect from one directory to another:

Redirect 301 "/old-directory" "http://www.new.com/new-directory"

Again, let’s take a close look at how this example can play out:

# Target URL Destination URL
1 http://old.com/old-directory/ http://www.new.com/new-directory/
2 http://old.com/old-directory/page.html http://www.new.com/new-directory/page.html
3 http://old.com/old-directory/sub-directory/ http://www.new.com/new-directory/sub-directory/
4 http://old.com/old-directory/sub-directory/page.html http://www.new.com/new-directory/sub-directory/page.html
5 http://old.com/old-directory/?q=21&id=902 http://www.new.com/new-directory/?q=21&id=902


  • Redirect #1 — Takes a directory on an old domain to a new directory on a new domain.
  • Redirect #2 — Shows that any individual pages within the old directory are automatically redirected to the same location in the new directory.
  • Redirect #3 — Shows that any subdirectories within the old directory are automatically redirected to the same location within the new directory.
  • Redirect #4 — Illustrates the recursive nature of the redirect.
  • Redirect #5 — Does the same thing as the first redirect, but demonstrates that URL GET parameters are always passed along as part of the redirect.

The assumption with this type of redirect is that the contents of the directory are exactly the same on the destination URL as they previously were on the target URL. In other words, only the directory name has changed.

It is possible to combine a directory redirect and a few single page redirects, like this:

Redirect 301 "/old-directory/about.html" "/new-directory/about-us.html"
Redirect 301 "/old-directory/contact.html" "/new-directory/contact-us.html"
Redirect 301 "/old-directory" "/new-directory"

This example shows that order is important. When a redirect rule is hit, it happens immediately. The rest of the file is not processed for redirect rules. If our single page redirects are not hit, then the generic directory redirect will happen. This is the proper way of handling a redirect where the contents of the directory are exactly the same on the destination URL as they previously were on the target URL, except for the about.html and contact.html pages.

Redirect an Entire Website

Here is an example of how you would redirect an entire site:

Redirect 301 "/" "http://www.new.com"

As you can see, this redirect assumes that everything on the new site is in the same place as it was on the old site:

# Target URL Destination URL
1 http://old.com/ http://www.new.com/
2 http://old.com/?q=21&id=902 http://www.new.com/?q=21&id=902
3 http://old.com/page.html http://www.new.com/page.html
4 http://old.com/directory/ http://www.new.com/directory/
5 http://old.com/directory/page.html http://www.new.com/directory/page.html
6 http://old.com/directory/sub-directory/ http://www.new.com/directory/sub-directory/


  • Redirect #1 — The old root domain redirects to the new root domain.
  • Redirect #2 — GET URL parameters are always passed along.
  • Redirect #3 — Single pages are redirected to the same location on the new domain.
  • Redirect #4 — Directories are redirected to the same location on the new domain.
  • Redirect #5 — Child pages are redirected to the same location on the new domain.
  • Redirect #6 — Subdirectories are redirected to the same location on the new domain.

Let me know if you found these examples helpful!

Hide WordPress Plugin Deactivation Links

You can prevent WordPress users from being able to deactivate plugins by hiding the ‘Deactivation’ links.


At first glance, it doesn’t make much sense to prevent users from disabling plugins within the WordPress admin. You certainly wouldn’t want to release a public plugin that prevents users from turning it off, especially since you have to deactivate a plugin before you can delete it. Let’s argue it out.

Me: “I think it would be appropriate to disable plugin deactivation when you have a plugin that is integral to the site and shouldn’t be disabled.”

You: “Yeah, but why not just use a must use plugin?!?”

Me: “If it is a custom plugin, absolutely. However, must use plugins don’t check for or show available updates.”

You: “Oh, so instead of having to manually update a must use plugin, if I disable deactivation for a public plugin then the user can’t disable it and can still see when updates are available.”

Me: “Right. You will also be able to update the plugin from the admin area, which you can’t do with a must use plugin.”

You: “It sounds like this would work well for publicly available plugins because they automatically check for updates. If I have a custom plugin that is set up to automatically check for updates, then this could be a great way to manage integral plugins across multiple client sites!”

Me: “Absolutely!”


The code below can be placed in a custom plugin. If you’ve wired the plugin for update checks, then you will get the benefits of easy updates without the downside of a client being able to disable or delete the plugin.

If you want to disable deactivation of a third-party plugin, then you will want to drop this code in a file in the mu-plugins directory and instead of using plugin_basename( __FILE__ ) you will want to hardcode the plugin basename.

WordPress Must Use Plugins

WordPress must use plugins are plugins that are always loaded and cannot be deactivated within the WordPress admin.


A bit of a hidden feature, must use plugins require a little setup initially:

  • First, you will need to create an mu-plugins directory within the WordPress content directory. The mu-plugins directory will sit alongside your normal plugins directory, normally at wp-content/mu-plugins unless you’ve moved or renamed the WordPress content directory.
  • Then, any files you upload into that directory will be loaded automatically. Just be aware that dropping a folder into mu-plugins won’t do anything, only standalone files will load.

The Pros

  • Must use plugins are always on and cannot be disabled within the admin area.
  • Must use plugins load before normal plugins.

The Cons

  • Must use plugins will never check for or show available updates.
  • Must use plugins do not trigger plugin activation hooks.

Tips & Tricks

  • Dropping a file into the mu-plugins directory means that code runs automatically, without having to log into the admin. This comes in handy when you need to do things like force a WordPress admin user.
  • If you want to load a normal plugin as a must use plugin, just drop the plugin folder into the mu-plugins directory and then create a plugin-loader.php file (any name will do) that does a require for the main plugin file.
  • Files in the mu-plugins directory load alphabetically. If you need to manually control the load order, you can simply rename the files.
  • You can move or rename the mu-plugins directory by setting these constants in your wp-config.php file:
define( 'WPMU_PLUGIN_DIR', __DIR__ . '/wp-content/required-plugins' );
define( 'WPMU_PLUGIN_URL', 'http://mydomain.com/wp-content/required-plugins' );

Exclude a Plugin or Theme from WordPress Updates

Exclude a Plugin

I’ve occasionally run into the situation where I’ve implemented a custom plugin for a client and given it a unique name. Then, someone else releases a plugin by the same name and now there is an ‘update’ for my plugin. The client goes in and updates the plugin only to find that they have lost the custom functionality I implemented for them.

If you create a custom plugin for a client, be sure to disable plugin updates!

Just place the code above in the main file of your plugin and WordPress will never show that it has an update.

Exclude a Theme

Another situation I’ve run into is where a new client comes to me and has a WordPress theme that was directly modified. Since they aren’t using a child theme, updating the theme would cause them to lose all the original customizations.

If you modify a publicly released theme, be sure to disable theme updates!

Note: This only works if the theme is active! If you want to prevent updates even if the theme is inactive, then you will need to add the code to a plugin and use hardcoded values instead of get_option( 'template' ) and get_option( 'stylesheet' ).

Both of the code snippets above are an updated version of the code originally posted by Mark Jaquith.

Managing WordPress Automatic Updates

WordPress will, by default, automatically update itself when there are security releases. It can also update themes and plugins automatically if necessary. From a security perspective, this is great. However, automatic updates can also present complications.

For example, if you’ve modified your theme, you don’t want it auto updating or you will lose all of those customizations. Perhaps you don’t want to update plugins automatically so you can test for potential conflicts with other plugins. Maybe you don’t want to update WordPress core because you are intentionally running an older version, as is often the case with large companies. The list of reasons goes on and on, so let’s just get to the code already.

Here is a list of what can be automatically updated:

  • WordPress core
  • Plugins
  • Themes
  • Translations

WordPress Core Updates

By default, WordPress will automatically update core if:

  • A new version is available and you are running a development version of WordPress.
  • A new version is available and it is a minor release.

WordPress, by default, won’t automatically update if:

  • A new version is available and it is a major release.
  • Version control is detected

WordPress has a constant called WP_AUTO_UPDATE_CORE which dictates how automatic updates to core are handled. Here are the possible values that can be set:

  • false — Prevents any automated updates to WordPress core.
  • minor — Allows updates to development versions and minor releases. This is the default.
  • true — Allows WordPress core to automatically update anytime there is a new version, whether it be a development, minor or major release.

To disable all automatic updates to WordPress core, you could just add this line to your wp-config.php file:

define( 'WP_AUTO_UPDATE_CORE', false );

After WordPress checks the value of the WP_AUTO_UPDATE_CORE constant, there are three filters that let you control whether or not automated updates happen, depending on the scenario:

  • allow_dev_auto_core_updates — Controls whether or not WordPress updates to a development release.
  • allow_minor_auto_core_updates — Controls whether or not WordPress updates to a minor release.
  • allow_major_auto_core_updates — Controls whether or not WordPress updates to a major release.

You can use the filters above to disable a specific type of update like this:

add_filter( 'allow_minor_auto_core_updates', '__return_false' );

If you want to enable a specific type, just replace __return_false with __return_true. Just in case you were wondering, these are functions that WordPress provides so you can easily set a callback that will return true or false.

Finally, there is one last filter that is run: auto_update_core. This controls whether or not core updates of any kind are run and defaults to the value determined after the WP_AUTO_UPDATE_CORE constant is checked and the aforementioned filters are run.

You can use this filter to disable core updates like this:

add_filter( 'auto_update_core', '__return_false' );

Plugin Updates

By default, WordPress plugins will only auto-update if the API response from WordPress.org passes an non-empty autoupdate property. This will only happen if the WordPress team makes the decision to update a plugin and ensures that the API response issues an auto-update command.

If you want to prevent this, there is only one filter that will allow you to do that:

add_filter( 'auto_update_plugin', '__return_false' );

Theme Updates

Like plugins, WordPress plugins will only auto-update if the API response from WordPress.org passes an non-empty autoupdate property because the core team decided to issue an auto-update command.

To prevent this, just use this filter:

add_filter( 'auto_update_theme', '__return_false' );

Translation Updates

WordPress translations are managed separately and are automatically updated by default. If you want to prevent this, just use this filter:

add_filter( 'auto_update_translation', '__return_false' );

All WordPress Updates

If you aren’t trying to selectively enable / disable specific types of updates and would rather just disable automatic updates of any kind, then you can use the AUTOMATIC_UPDATER_DISABLED constant in your wp-config.php file to do just that:


Setting the value to true will disable all automatic updates. The default value is false.

After the constant is checked, a filter by the same name is called: automatic_updater_disabled. This filter is your last chance to override the defaults, or any value that may have been set via the constant.

You can disable all automatic updates using the filter, like this:

add_filter( 'automatic_updater_disabled', '__return_true' );

There is one other, slightly nuclear, way to disable automatic updates: DISALLOW_FILE_MODS. When set to true this constant will do the following:

  • Disable automatic updates of any kind.
  • Disable the theme editor.
  • Disable the plugin editor.
  • Disable the ability to install themes or plugins.
  • Prevent all users from being able to update WordPress core, themes or plugins from the admin.
  • Hide all update notifications for themes and plugins.
  • Will prevent any and all the constants and filters mentioned earlier in this article from having any effect.

This doesn’t really make sense to use unless a site is completely managed by a professional developer or team of developers and there are systems in place where all updates are handled external to the production code base.

If you know what you are doing and want to go this route, just add this to your wp-config.php file:

define( 'DISALLOW_FILE_MODS', true );

Note: The WordPress core update nag will still display, but it will simply tell users to notify the site administrator. So, if you do choose to go this route, you will probably want to disable that nag as well.

Disable WordPress Updates for Specific Users

Why would you ever want to disable WordPress updates? It is extremely important that you keep WordPress and your themes and plugins up-to-date!

Yes, but it may be a good idea to disable WordPress updates in some situations. Running an update routine in WordPress is pretty reliable, but it is prudent to take some steps to make sure things don’t break. For starters, you’ll want to be sure you’ve backed up the site’s files and database for when you need to rollback.

With great power comes great responsibility.

— Uncle Ben

If you have clients that have the power to run WordPress updates, they also have the power to potentially damage the site. If you are running automated backups and can give them the ability to quickly and easily restore the site, then it probably isn’t a big deal. Otherwise, the responsibility of properly backing up the site before an update falls on them.

If you choose to disable WordPress updates, here’s how:

 * Disable WordPress updates
 * @return object
function disable_updates() {
    global $wp_version;
    return (object) array( 'last_checked' => time(), 'version_checked' => $wp_version, );

add_action( 'init', function () {
    if ( ! current_user_can( 'administrator' ) ) {
        add_filter( 'pre_site_transient_update_core', 'disable_updates' );     // Disable WordPress core updates
        add_filter( 'pre_site_transient_update_plugins', 'disable_updates' );  // Disable WordPress plugin updates
        add_filter( 'pre_site_transient_update_themes', 'disable_updates' );   // Disable WordPress theme updates
} );

In this example, only administrators can see the update notifications and update a plugin. However, you may need to adapt the criteria to meet your own needs.

Note: If you go down this path, then be sure to actually login and update the client’s site!

Featured image by GotCredit

Easily Check if Multiple Array Keys Exist in PHP

Today I found myself needing to check an associative array to see if it contained a specific set of keys.

Here is what I was wanting to do:

if( isset( $data['sanitize'], $data['validate'], $data['authorize'] ) ) {
    // Do stuff with my special array data

Granted, it isn’t a whole lot of code, but syntax like this just drives me nuts. So, I thought, wouldn’t it be nice to do something like this instead:

if( array_keys_exist( $data, 'sanitize', 'validate', 'authorize' ) ) {
    // Do stuff with my special array data

This plays off of the well known array_key_exists() function in PHP, but adds in the ability to check if multiple keys exist and improves the readability of the code.

So, moments later, I put together a nice little utility function that does just that:

 * Checks if multiple keys exist in an array
 * @param array $array
 * @param array|string $keys
 * @return bool
function array_keys_exist( array $array, $keys ) {
    $count = 0;
    if ( ! is_array( $keys ) ) {
        $keys = func_get_args();
        array_shift( $keys );
    foreach ( $keys as $key ) {
        if ( isset( $array[$key] ) || array_key_exists( $key, $array ) ) {
            $count ++;

    return count( $keys ) === $count;


Filtering Multi-Dimensional Arrays in PHP

Filtering empty values from an array in PHP is best done using array_filter(). Let’s take a simple array, for example:

$data = array( null, true, false, 0, 1, 11, '', 'test' );

Now, let’s run it through array_filter():

array_filter( $data );

What we end up with is this:

array( true, 1, 11, 'test' );

Awesome! Now what happens when we need to filter this array?

$data = array(
    <span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="param "><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="param ">array(),

Well, PHP doesn’t provide a way to recursively filter a multi-dimensional array.

Here is a simple utility function that works exactly like array_filter(), but is recursive:

 * Recursively filter an array
 * @param array $array
 * @param callable $callback
 * @return array
function array_filter_recursive( array $array, callable $callback = null ) {
    $array = is_callable( $callback ) ? array_filter( $array, $callback ) : array_filter( $array );
    foreach ( $array as &$value ) {
        if ( is_array( $value ) ) {
            $value = call_user_func( __FUNCTION__, $value, $callback );

    return $array;

The function works just like PHP’s array_filter() and even allows for a custom callback if you want to provide one. Running our multidimensional array through array_filter_recursive() returns this result: