Blog

First Steps to WordPress Security

WordPress security is an important consideration, but often site owners don’t think about it until it is too late. Like most things, the Pareto principle applies: If you can do a few simple things (20%), you can prevent most security issues (80%). In addition to prevention, you can also take a few simple steps that will help ensure you are able to restore your site in the event that it is compromised, corrupted, or even just accidentally deleted.

This article isn’t really targeted at developers, but rather non-technical site owners who want to make sure they have at least done the bare minimum when it comes to security. On that note, here are my quick and dirty recommendations:

  • Keep WordPress up to date. – Older versions of WordPress are more likely to be hacked as they don’t include many of the security fixes that newer versions do. Just be sure you update all of your plugins as well as your theme before upgrading WordPress core to avoid potential issues when upgrading.
  • Keep your theme and plugins up to date. – Again, older versions are more likely to be hacked as they don’t include many of the security fixes that newer versions do.
  • Only use quality themes and plugins. – WordPress core is very secure and most often it is a bad theme or poorly coded plugin that makes your site vulnerable. Make sure that the software you install is of reasonable quality. If you are using a theme or plugins from the WordPress repository, you can do a little checking to see the last time it was updated, what types of reviews its had and how many downloads or active installs its had. For premium themes and plugins, you’ll just have to do more research on the company and ask experienced WordPress developers and/or users if the company and plugin are reputable. As a rule of thumb: Never download free plugins that aren’t listed in the WordPress repository.
  • Set a secure password and change it often. – You should change your password about once every one to three months. Hackers have scripts that will try to guess your password. A more secure password will be harder to guess and changing your password occasionally will ensure that they have to start the guessing process over again and aren’t given an unlimited amount of time to discover your password. If an easy to remember password is important to you, then create an admin user with a super-complicated password and then create another user that has the ‘Editor’ role. You can then use that ‘Editor’ role to log in and do your normal content publishing. However, I’d recommend just using a tool like LastPass to help you keep track of your passwords.
  • Backup your site regularly. – If your site is ever hacked or otherwise lost, at least you will be able to restore it. Obviously, your backup schedule will depend on how often you update the site and/or perform software upgrades. Be sure that you backup not only the filesystem, but also the database. There are some great tools out there for handling backups, like these:
  • Make sure your site backups are stored offsite. – In other words, don’t count on or trust backups from your web host or backups that are stored on your site’s server. Your data needs to be somewhere that a hacker isn’t likely to delete it if they gained access to the server. Ideally, your web host will also provide backups so that you have some built-in redundancy.

If you do these things, you will be well on your way to securing your site and will be able to easily recover if something does happen. This is by no means an exhaustive list, so once you’ve taken care of these things be sure to check out the Next Steps section below.

If you are technical enough to add a line to your wp-config.php file, you will get bonus points for adding this line to disable the WordPress file editor:

define('DISALLOW_FILE_EDIT', true);

Next Steps

The tips listed here cover just a few things you can do to improve security. Here are a few resources to help you take security to the next level:

Add Helper Classes to WordPress Navigation Menus

WordPress automatically outputs many helpful CSS class names for menus. If you use the wp_nav_menu() function to display your menus, as all good themes should do, you don’t have to settle for just the default class names.

The wp_nav_menu() function calls the wp_nav_menu_objects filter, which allows you to manipulate the menu objects before being converted into HTML and displayed on the site. One of the things you can do is add a few custom class names, like this:

This adds a menu-item-first class to the first menu item and a menu-item-last class to the last menu item. These extra utility classes are helpful when styling menus in a custom theme.

Prevent Directory Browsing with .htaccess

Directory browsing allows visitors to your site to see and browse through the contents of folders on your web site. Anyone on the web could potentially visit a directory on your site, see what files exist there and open them at will. Typically, web hosts disable directory browsing for security reasons. However, there are still plenty of web hosts out there that don’t disable it.

Basically, if directory browsing is enabled and you don’t have an index.html or index.php file in a given directory, the web browser will display the contents of the directory along with a link back to the parent directory.

Obviously, revealing the inner workings of your website to the public could entice hackers or at least make their job easier. Hackers can perform Google searches to find sites with directory browsing enabled and then choose sites which have known vulnerabilities based on their findings.

How to Check Your Site

You can check to see if directory browsing is enabled on your site by creating a folder and adding a basic text file. If you visit the directory in your web browser and it displays a link to the text file, then directory browsing is enabled. If you get a ‘Page Not Found’ or ‘Forbidden’ message, then directory browsing is disabled.

Web Host Checklist

If you find that your web host has directory browsing enabled, leave a comment below and we’ll start a running list. Meanwhile, if your web host uses an Apache server, you will want to apply the fix at the end of this article.

Directory Browsing and WordPress

By default, a self-hosted installation of WordPress has a built-in safeguard against directory browsing. A new WordPress installation will contain a blank index.php file in each folder so that a user visiting a folder, such as the plugins directory, will be presented with a blank screen. However, many WordPress plugins don’t do this. This means that hackers can likely see what plugins, and versions of those plugins, that you have installed on your site.

Securing Access to your Directories with .htaccess

The easiest way to disable directory browsing is to add a line to your site’s .htaccess file. Just be aware that this only works for sites running on an Apache web server.

Keep in mind that you can have .htaccess files in multiple locations, but you want to make your change in the .htaccess found in the root directory for your domain. This will cause the change to take place across your entire site.

Here is what you will need to do:

  • Download your .htaccess file and make a copy. You should always keep a copy of your .htaccess file when making changes, for when things don’t work as planned.
  • Add these lines to your .htaccess file:

  • Upload the new .htaccess file and overwrite the existing one.
  • Verify that directory browsing is disabled. You can visit a folder that previously allowed you to view the directory contents and be sure you are getting a ‘Page Not Found’ or ‘Forbidden’ error message.

Browser Caching of 301 Redirects

Many people don’t realize that browsers cache 301 redirects. A 301 redirect is a permanent redirect from one URL to another. It only makes sense that a browser should cache a 301 redirect, after all, it is permanent.

Our natural tendency after setting up redirects is to check and see if they are working properly. However, if you made a mistake and had to tweak one of your 301 redirect rules, guess what is going to happen when you go to test your change? Yep. Your browser is going to send you to the same place it redirected you to last time.

Clear your browser cache each and every time you make a change to a 301 redirect.

If you put a 301 redirect into operation, that redirect will be cached in the browser for any visitor’s on your site. You can’t clear the browser cache for your users, so if you need to change or undo a 301 redirect, the old redirect is still going to be in effect until their cache expires.

Never put a 301 (permanent) redirect in place unless it is truly permanent!

When you are setting up redirects and aren’t sure if they work yet, do you really want them to be permanent? No, you still need to test them! As a best practice, you should always implement 302 (temporary) redirects first. This way you will avoid having to constantly clear your browser cache and you will never have a situation where a subset of your users are constantly redirected into oblivion because their browser cached a bad redirect.

Always implement 302 (temporary) redirects first, then change them to 301 (permanent) redirects once you’ve tested them!

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:

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:

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:

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

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

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:

  • 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:

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:

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

Explanation:

  • 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:

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

Explanation:

  • 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:

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:

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/

Explanation:

  • 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.

Why

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!”

How

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.

Setup

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:

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.