Tips and Tricks - How to Secure WordPress

March 27, 2012

As a hobby, I dabble in WordPress, so I thought I'd share a few security features I use to secure my WordPress blogs as soon as they're installed. Nothing in this blog will be earth-shattering, but because security is such a priority, I have no doubt that it will be useful to many of our customers. Often, the answer to the question, "How much security do I need on my site?" is simply, "More," so even if you have a solid foundation of security, you might learn a new trick or two that you can incorporate into your next (or current) WordPress site.

Move wp-config.php

The first thing I do is change the location of my wp-config.php. By default, it's installed in the WordPress parent directory. If the config file is in the parent directory, it can be viewed and accessed by Apache, so I move it out of web/root. Because you're changing the default location of a pretty significant file, you need to tell WordPress how to find it in wp-load.php. Let's say my WordPress runs out of /webroot on my host ... I'd need to make a change around Line 26:

if ( file_exists( ABSPATH . 'wp-config.php') ) {
 
        /** The config file resides in ABSPATH */
        require_once( ABSPATH . 'wp-config.php' );
 
} elseif ( file_exists( dirname(ABSPATH) . '/wp-config.php' ) && ! file_exists( dirname(ABSPATH) . '/wp-settings.php' ) ) {
 
        /** The config file resides one level above ABSPATH but is not part of another install*/
        require_once( dirname(ABSPATH) . '/wp-config.php' );

The code above is the default setup, and the code below is the version with my subtle update incorporated.

if ( file_exists( ABSPATH . 'wp-config.php') ) {
 
        /** The config file resides in ABSPATH */
        require_once( ABSPATH . '../wp-config.php' );
 
} elseif ( file_exists( dirname(ABSPATH) . '..//wp-config.php' ) && ! file_exists( dirname(ABSPATH) . '/wp-settings.php' ) ) {
 
        /** The config file resides one level above ABSPATH but is not part of another install*/
        require_once( dirname(ABSPATH) . '../wp-config.php' );

All we're doing is telling the application that the wp-config.php file is one directory higher. By making this simple change, you ensure that only the application can see your wp-config.php script.

Turn Down Access to /wp-admin

After I make that change, I want to turn down access to /wp-admin. I allow users to contribute on some of my blogs, but I don't want them to do so from /wp-admin; only users with admin rights should be able to access that panel. To limit access to /wp-admin, I recommend the plugin uCan Post. This plugin creates a page that allows users to write posts and submit them within your theme.

But won't a user just be able to navigate to http://site.com/wp-admin? Yes ... Until we add a simple function to our theme's functions.php file to limit that access. At the bottom of your functions.php file, add this:

############ Disable admin access for users ############

add_action('admin_init', 'no_more_dashboard');
function no_more_dashboard() {
  if (!current_user_can('manage_options') && $_SERVER['DOING_AJAX'] != '/wp-admin/admin-ajax.php') {
  wp_redirect(site_url()); exit;
  }
}
 
###########################################################

Log in as a non-admin user, and you'll get redirected to the blog's home page if you try to access the admin panel. Voila!

Start Securing the WordPress Database

Before you go any further, you need to look at WordPress database security. This is the most important piece in my opinion, and it's not just because I'm a DBA. WordPress never needs all permissions. The only permissions WordPress needs to function are ALTER, CREATE, CREATE TEMPORARY TABLES, DELETE, DROP, INDEX, INSERT, LOCK TABLES, SELECT and UPDATE.

If you run WordPress and MySQL on the same server the permissions grant would look something like:

GRANT ALTER, CREATE, CREATE TEMPORARY TABLES, DELETE, DROP, INDEX, INSERT, LOCK TABLES, SELECT, UPDATE ON <DATABASE>.* TO <USER>@'localhost' IDENTIFIED BY '<PASSWORD>';

If you have a separate database server, make sure the host of the webserver is allowed to connect to the database server:

GRANT ALTER, CREATE, CREATE TEMPORARY TABLES, DELETE, DROP, INDEX, INSERT, LOCK TABLES, SELECT, UPDATE ON <DATABASE>.* TO <USER>@'<ip of web server' IDENTIFIED BY '<PASSWORD>';

The password you use should be random, and you should not need to change this. DO NOT USE THE SAME PASSWORD AS YOUR ADMIN ACCOUNT.

By taking those quick steps, we're able to go a long way to securing a default WordPress installation. There are other plugins out there that are great tools to enhance your blog's security, and once you've got the fundamental security updates in place, you might want to check some of them out. Login LockDown is designed to stop brute force login attempts, and Secure WordPress has some great additional features.

What else do you do to secure your WordPress sites?

-Lee

Comments

March 27th, 2012 at 11:15am

Good day, Lee:

As a SoftLayer customer and technology partner, it is good to see SoftLayer more actively bloging.

From http://www.dynamicnet.net/2012/03/installing-wordpress-securely/ I would also recommend changing the WordPress mysql table prefix from the default of "wp_" to a random 5 to 12 wide prefix along with making sure the mysql password is complex.

In the best case, server / virtual machine the site is hosted on only allows TCP 3306 (mySQL default port) access from within the network and only authorized, static, external IP addresses.

http://codex.wordpress.org/Hardening_WordPress has further steps that one can take as well.

Thank you.

March 27th, 2012 at 1:16pm

Peter,
Thank you, your points are spot on. Here is another note: Disable directory browsing. By default in most installations of Apache, index of directories are shown in web browsers.

Modifying this behavior is easy with Apache, just add the following line of code to the .htaccess file in the root directory (In the webroot).

Options All -Indexes

--Lee

March 28th, 2012 at 6:35am

Hi Lee

Thanks for the great tips. I have only just started using wordpress and have been actively looking for some good security tips. I have seen these tips mentioned anywhere before. Good job.

I have taken to restricting access to the wp-admin folder by IP address using the .htaccess file

March 28th, 2012 at 12:26pm

Steve,
Thank you, Im glad to help. The .htaccess was the original way of protecting the admin section I used. I changed it as I have multiple admins and wanted the access to be automated. I found a small issue with the way .htacess works with some includes. PHP includes are not running over http, but rather over the filesystem. This is still a great way to secure the wp-admin directory.

--Lee

April 4th, 2012 at 11:49am

I really like those tips, as a person who I had one my wordpress sites being hacked, the above tips, must be implemented.
In addition to that, I use some plugins that also keep me safe, and keep in the loop with all hacking attempts, and what's going on with my wordpress sites.

I created a quick screencast that shows you how:
http://getyourblogready.com/secure-wordpress-login-avoid-be-hacked/

Check it out, and let me know, what you think...

Stay safe...

June 15th, 2012 at 11:26pm

I tried to make the wp-config move to the root above my public_html directory but got an error:

Warning: file_exists() [function.file-exists]: open_basedir restriction in effect. File(/home/username./wp-config.php) is not within the allowed path(s): (/home/username:/usr/lib/php:/usr/local/lib/php:/tmp) in /home/username/public_html/wp-load.php on line 31

Warning: require(/home/username/public_html/WPINC/option.php) [function.require]: failed to open stream: No such file or directory in /home/username/public_html/wp-includes/functions.php on line 8

Fatal error: require() [function.require]: Failed opening required '/home/username/public_html/WPINC/option.php' (include_path='.:/usr/lib/php:/usr/local/lib/php') in /home/username/public_html/wp-includes/functions.php on line 8

when I tried to browse to the site after the following changes:

if ( file_exists( ABSPATH . 'wp-config.php') ) {

/** The config file resides in ABSPATH */
require_once( ABSPATH . '../wp-config.php' );

} elseif ( file_exists( dirname(ABSPATH) . '..//wp-config.php' ) && ! file_exists( dirname(ABSPATH) . '/wp-settings.php' ) ) {

/** The config file resides one level above ABSPATH but is not part of another install*/
require_once( dirname(ABSPATH) . '../wp-config.php' );

September 22nd, 2012 at 4:36am

It is not necessary to move wp-config and break WordPress.
Just add this to .htaccess in the main dir.

<files wp-config.php>
order allow,deny
deny from all
</files>

I also set the security of the file to 600 or something like that.

January 7th, 2013 at 9:08am

Very informative and important tutorial for the WordPress user. Thanks forsharing

November 10th, 2013 at 9:10am

This ia a good review on securing wordpress. one other simple thing I usually do is to move my htaccess file from its default location in cpanel.

Leave a Reply

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <pre>, <blockcode>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Comments

March 27th, 2012 at 11:15am

Good day, Lee:

As a SoftLayer customer and technology partner, it is good to see SoftLayer more actively bloging.

From http://www.dynamicnet.net/2012/03/installing-wordpress-securely/ I would also recommend changing the WordPress mysql table prefix from the default of "wp_" to a random 5 to 12 wide prefix along with making sure the mysql password is complex.

In the best case, server / virtual machine the site is hosted on only allows TCP 3306 (mySQL default port) access from within the network and only authorized, static, external IP addresses.

http://codex.wordpress.org/Hardening_WordPress has further steps that one can take as well.

Thank you.

March 27th, 2012 at 1:16pm

Peter,
Thank you, your points are spot on. Here is another note: Disable directory browsing. By default in most installations of Apache, index of directories are shown in web browsers.

Modifying this behavior is easy with Apache, just add the following line of code to the .htaccess file in the root directory (In the webroot).

Options All -Indexes

--Lee

March 28th, 2012 at 6:35am

Hi Lee

Thanks for the great tips. I have only just started using wordpress and have been actively looking for some good security tips. I have seen these tips mentioned anywhere before. Good job.

I have taken to restricting access to the wp-admin folder by IP address using the .htaccess file

March 28th, 2012 at 12:26pm

Steve,
Thank you, Im glad to help. The .htaccess was the original way of protecting the admin section I used. I changed it as I have multiple admins and wanted the access to be automated. I found a small issue with the way .htacess works with some includes. PHP includes are not running over http, but rather over the filesystem. This is still a great way to secure the wp-admin directory.

--Lee

April 4th, 2012 at 11:49am

I really like those tips, as a person who I had one my wordpress sites being hacked, the above tips, must be implemented.
In addition to that, I use some plugins that also keep me safe, and keep in the loop with all hacking attempts, and what's going on with my wordpress sites.

I created a quick screencast that shows you how:
http://getyourblogready.com/secure-wordpress-login-avoid-be-hacked/

Check it out, and let me know, what you think...

Stay safe...

June 15th, 2012 at 11:26pm

I tried to make the wp-config move to the root above my public_html directory but got an error:

Warning: file_exists() [function.file-exists]: open_basedir restriction in effect. File(/home/username./wp-config.php) is not within the allowed path(s): (/home/username:/usr/lib/php:/usr/local/lib/php:/tmp) in /home/username/public_html/wp-load.php on line 31

Warning: require(/home/username/public_html/WPINC/option.php) [function.require]: failed to open stream: No such file or directory in /home/username/public_html/wp-includes/functions.php on line 8

Fatal error: require() [function.require]: Failed opening required '/home/username/public_html/WPINC/option.php' (include_path='.:/usr/lib/php:/usr/local/lib/php') in /home/username/public_html/wp-includes/functions.php on line 8

when I tried to browse to the site after the following changes:

if ( file_exists( ABSPATH . 'wp-config.php') ) {

/** The config file resides in ABSPATH */
require_once( ABSPATH . '../wp-config.php' );

} elseif ( file_exists( dirname(ABSPATH) . '..//wp-config.php' ) && ! file_exists( dirname(ABSPATH) . '/wp-settings.php' ) ) {

/** The config file resides one level above ABSPATH but is not part of another install*/
require_once( dirname(ABSPATH) . '../wp-config.php' );

September 22nd, 2012 at 4:36am

It is not necessary to move wp-config and break WordPress.
Just add this to .htaccess in the main dir.

<files wp-config.php>
order allow,deny
deny from all
</files>

I also set the security of the file to 600 or something like that.

January 7th, 2013 at 9:08am

Very informative and important tutorial for the WordPress user. Thanks forsharing

November 10th, 2013 at 9:10am

This ia a good review on securing wordpress. one other simple thing I usually do is to move my htaccess file from its default location in cpanel.

Leave a Reply

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <pre>, <blockcode>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.