Security Tips and Tricks

Recently I’ve been trying to update all the outdated tutorials on my blog and I found out tons of security tips and tricks I published in the past in separate tutorials. I decided to combine them into all-in-one security guide for WordPress users and also developers.

Maybe some you are already familiar with everything I am going to mention below but really, the site security starts with basic things guys.

Contact us if you need help protecting your website or restoring it after attacks.

1. Downloading premium themes and plugins “for free”

Let’s don’t forget that free cheese is only in a mousetrap, so when you’re downloading for example WooCommerce Memberships not from woocommerce.com but from some weird random website, ask yourself – what is their interest here? Tons of ads on their website? Probably, but it isn’t that profitable, so most likely there is some hidden interest, for example to gain the access to your website and show the ads there without you knowing. Much more interesting, isn’t it?

I have to confess – once I downloaded a WordPress plugin from CodeCanyon for free and I didn’t even want to use the plugin, I was just curious how one specific feature is implemented in it. So, I had installed it on my localhost and guess what? All my localhost websites had been infected afterwards!

Example:

WooCommerce memberships download example
Don’t buy into this shirt.

2. Using passwords like “qwerty12345”

I assume you are already tired of this 😁 A lot of websites out there require a strong password and when you create an account, they don’t accept only numbers and sometimes even require special characters and upper-case letters. I am tired of this as well!

But the truth is – passwords are the weakest part of your WordPress site security after broken plugins and themes.

Below are some of the tips for creating a strong password from WordPress:

  • Use a password manager application which will generate a password for you and store it in a secure database on your device.
  • If you are not comfortable with an application which stores your passwords, you can use passphrases – the passwords based on a random collection of words rather than just one, for example: elevate dust trap satoshi. In order to generate these random words you can use this tool by the way. You can also add extra strength to it if you add some upper-case letters or random symbols and numbers, example elevatE7 dust trap satoshi (#). Of course don’t use any predictable patterns like song lyrics etc.

3. Protecting WordPress login page

Now we have a strong password which is great but don’t forget that nowadays the most recent password attacks can attempt up to 350 billion guesses per second, so what about a little bit extra security?

These type of attacks are called brute-force attacks and our goal is to limit the number of guesses up to 5 per hour for example or if you are the only person who are using the login page, it would be even better to block it by IP.

Installing a plugin to prevent brute force login attemps

This is the easiest way. But also super-effective.

For example on some of my projects I am using Simple Login Lockdown by Christopher Davis. Though this plugin is quite outdated it is still working great! If you’re concerned about it, of course you can choose a more “fresh” plugin.

Once you installed this plugin on your website, you just need to go to Settings > Reading and configure how many login attempts to allow from a specific IP address within a specific timeframe.

Simple login lockdown plugin settings
5 attempts per hour seems pretty good for me.

As an option you can also use plugins which allow to enable 2FA authentication on your WordPress login page (Google Authenticator for example).

Restrict WordPress admin area by IP addresses

What could be more effective than blocking the whole /wp-admin directory by IP address? I mean allowing access to it only for specific IP addresses or IP address range.

In that case you will need to create a .htaccess file in your /wp-admin directory with the following content:

Order Deny,Allow
Deny from all
Allow from 88.227.56.113
Allow from 88.227.56.114

<Files "admin-ajax.php">
	Order Deny,Allow
	Allow from all
</Files>

Don’t forget to allow the access to admin-ajax.php file, because frontend AJAX requests may depend on it.

With the help of .htaccess it also possible to add one more extra layer of login and password which is also better than nothing of course, but I believe an IP block is much more effective and I recommend to use it whenever possible.

4. Administrator usernames

Don’t use “admin” default username

When you install WordPress, during the installation process as a username please select anything but not “superadmin”, “administrator” and especially “admin”.

WordPress installation page, username selection
“admin” was prefilled as a default username for quite a long time in WordPress. Should I explain that using it can put your website at risk?

If your WordPress is already installed and you already have “admin” username, then I have bad news for you – you can not change it in your user profile settings. But the good news is that it is still possible, we just need to change it manually in the database.

You can do it by running the following query:

UPDATE wp_users SET user_login='notadminpls' WHERE user_login='admin';

Please don’t forget to replace wp_ with your custom database prefix. Please tell me that you’re not using a default database prefix.

For WordPress Multisite sites one more SQL query is needed:

UPDATE wp_sitemeta SET meta_value = REPLACE(meta_value, 's:5:"admin"', 's:11:"notadminpls"') WHERE meta_key = 'site_admins';

Please be careful with serialized data here, I mean this line s:11:"notadminpls" – 11 is the number of characters in our new username, if you put the wrong number… well better not to know what could happen.

Completely hide administrator username from the website

Okay… if you’ve changed your admin login, maybe you feel relieved now. Not yet my friend.

Try to add to your website homepage URL something like this /?author=1. Press “Enter”. The page is performing redirect… and… isn’t it your new login?

Surprised?

So, first of all we will close this /?author= by redirecting it in .htaccess (so it will be impossible to find out any user login by the ID) and we will also change your administrator nicename, which is used in author archive URLs /author/NICENAME.

The code below should be placed right after the line RewriteEngine On and before the line RewriteBase /.

RewriteCond %{REQUEST_URI} !^/wp-admin [NC]
RewriteCond %{QUERY_STRING} author=\d
RewriteRule ^.*$ - [R=404,L]

The final touches, let’s go to Users > Profile and change the “Display name”:

changing WordPress user display name

It is not so simple to change the nicename as the display name (you have to do stuff in the database again) but your nicename is displayed in body_class(), in comment_class() and in admin author archive URLs /author/MishaRudrastyh. We are about to change it by running the following SQL query:

UPDATE wp_users SET user_nicename = 'MishaRudrastyh' WHERE ID = 1;

Once again, do not forget about your database prefix here.

5. Remove every deactivated plugin and theme from your website

That’s actually a pretty simple step, just keep in mind two things:

  • Not using a plugin or a theme on your website but have them installed in your WordPress dashboard? Please remove them, because potentionally the vulnerable code could be inside this specific plugin or theme you are keeping on your website for no reason.
  • Having tons of plugins on your website and need all of them? Please consider replacing some of them with customly coded features specifically for your project. Need help with that – me and my team are happy to help you, just contact us.

6. Database security tips

Change Database Prefix

Before we dive into this chapter of the tutorial, I think it is also worth mentioning that it is not recommended to run multiple WordPress installations on the same database. Really — if someone gets access to one of the databases he gets access to all your websites. You can find this recommendation in official WordPress codex as well. This is not about WordPress Multisite by the way.

Now let’s talk about table prefixes.

If you haven’t installed your WordPress website yet, of course you can set a custom prefix there. Just use anything except wp_ there.

Set custom database prefix in WordPress
So I changed wp_ to notwpplease_.

Everything is simple here, but if your WordPress is already installed. It is possible to change table prefixes that way? Or is it too late?

Of course it is still possible to do but in multiple steps. First of all please open your wp-config.php and find these lines there:

/**
 * WordPress database table prefix.
 *
 * You can have multiple installations in one database if you give each
 * a unique prefix. Only numbers, letters, and underscores please!
 */
$table_prefix = 'wp_';

Let’s change the prefix there. But I don’t recommend you to save that file immediately because after that your WordPress website will start to suggest a clean installation on its every page. So, if it is live right now, this shouldn’t happen.

$table_prefix = 'wp_hj87ka_';

Why does it happen? Well, our database table still have old names like wp_posts, wp_options etc. So, our goal is to rename them first and only after that we have to save changes in wp-config.php.

RENAME TABLE wp_comments TO wp_hj87ka_comments;
RENAME TABLE wp_commentmeta TO wp_hj87ka_commentmeta;
RENAME TABLE wp_links TO wp_hj87ka_links;
RENAME TABLE wp_options TO wp_hj87ka_options;
RENAME TABLE wp_postmeta TO wp_hj87ka_postmeta;
RENAME TABLE wp_posts TO wp_hj87ka_posts;
RENAME TABLE wp_terms TO wp_hj87ka_terms;
RENAME TABLE wp_termmeta TO wp_hj87ka_termmeta;
RENAME TABLE wp_term_relationships TO wp_hj87ka_term_relationships;
RENAME TABLE wp_term_taxonomy TO wp_hj87ka_term_taxonomy;
RENAME TABLE wp_usermeta TO wp_hj87ka_usermeta;
RENAME TABLE wp_users TO wp_hj87ka_users;

Once you run those queries and everything is ok, also run a couple more:

UPDATE wp_hj87ka_options SET option_name = REPLACE(option_name, 'wp_', 'wp_hj87ka_') WHERE option_name LIKE 'wp_%';
UPDATE wp_hj87ka_usermeta SET meta_key = REPLACE(meta_key, 'wp_', 'wp_hj87ka_') WHERE meta_key LIKE 'wp_%';

Done? Save wp-config.php file now!

There is also a tool on my website which allows to generate all these queries!

Please keep in mind that each plugin you’re using may have its own tables in the database. You have to rename those tables as well.

WooCommerce is a widely used shopping plugin for WordPress, so I decided to mention queries for it.

RENAME TABLE wp_woocommerce_api_keys TO wp_hj87ka_woocommerce_api_keys;
RENAME TABLE wp_woocommerce_attribute_taxonomies TO wp_hj87ka_woocommerce_attribute_taxonomies;
RENAME TABLE wp_woocommerce_downloadable_product_permissions TO wp_hj87ka_woocommerce_downloadable_product_permissions;
RENAME TABLE wp_woocommerce_order_itemmeta TO wp_hj87ka_woocommerce_order_itemmeta;
RENAME TABLE wp_woocommerce_order_items TO wp_hj87ka_woocommerce_order_items;
RENAME TABLE wp_woocommerce_payment_tokenmeta TO wp_hj87ka_woocommerce_payment_tokenmeta;
RENAME TABLE wp_woocommerce_payment_tokens TO wp_hj87ka_woocommerce_payment_tokens;
RENAME TABLE wp_woocommerce_sessions TO wp_hj87ka_woocommerce_sessions;
RENAME TABLE wp_woocommerce_shipping_zones TO wp_hj87ka_woocommerce_shipping_zones;
RENAME TABLE wp_woocommerce_shipping_zone_locations TO wp_hj87ka_woocommerce_shipping_zone_locations;
RENAME TABLE wp_woocommerce_shipping_zone_methods TO wp_hj87ka_woocommerce_shipping_zone_methods;
RENAME TABLE wp_woocommerce_tax_rates TO wp_hj87ka_woocommerce_tax_rates;
RENAME TABLE wp_woocommerce_tax_rate_locations TO wp_hj87ka_woocommerce_tax_rate_locations;

More queries for WordPress Multisite are coming :)

RENAME TABLE wp_blogs TO wp_hj87ka_blogs;
RENAME TABLE wp_blogmeta TO wp_hj87ka_blogmeta;
RENAME TABLE wp_registration_log TO wp_hj87ka_registration_log;
RENAME TABLE wp_site TO wp_hj87ka_site;
RENAME TABLE wp_signups TO wp_hj87ka_signups;
RENAME TABLE wp_sitemeta TO wp_hj87ka_sitemeta;

For each site of the network repeat the following replacements:

RENAME TABLE wp_2_comments TO wp_hj87ka_2_comments;
RENAME TABLE wp_2_commentmeta TO wp_hj87ka_2_commentmeta;
RENAME TABLE wp_2_links TO wp_hj87ka_2_links;
UPDATE wp_2_options SET option_name = REPLACE(option_name, 'wp_', 'wp_hj87ka_') WHERE option_name LIKE 'wp_%';
RENAME TABLE wp_2_options TO wp_hj87ka_2_options;
RENAME TABLE wp_2_postmeta TO wp_hj87ka_2_postmeta;
RENAME TABLE wp_2_posts TO wp_hj87ka_2_posts;
RENAME TABLE wp_2_terms TO wp_hj87ka_2_terms;
RENAME TABLE wp_2_termmeta TO wp_hj87ka_2_termmeta;
RENAME TABLE wp_2_term_relationships TO wp_hj87ka_2_term_relationships;
RENAME TABLE wp_2_term_taxonomy TO wp_hj87ka_2_term_taxonomy;

Add here WooCommerce queries for each site of the network where WooCommerce is installed or for any plugin with the custom tables you’re using.

Prevent SQL injections in WordPress

Maybe you heard that most WordPress security problems come from bad-written themes and plugins. And when you create your own plugin or a theme you can create another security hole, like a possibility of SQL-injection.

Please remember these simple tips to stay protected.

  1. Always use $wpdb to connect to MySQL.
  2. It is required to run $wpdb->query(), $wpdb->get_col(), $wpdb->get_var(), $wpdb->get_row(), $wpdb->get_results() with $wpdb->prepare() !
  3. On the other hand, $wpdb->insert(), $wpdb->update(), $wpdb->replace(), $wpdb->delete() shouldn’t be wrapped with $wpdb->prepare() because it is already inside them.

The example of $wpdb->get_results() with $wpdb->prepare():

// How to get all published pages
global $wpdb;

// these variables we get earlier somewhere in the code, maybe it is form data
$page_author_id = 1;
$post_type_name = 'page';

// the usage of $wpdb->prepare() statement
$pages = $wpdb->get_results( 
	$wpdb->prepare( 
		"
		SELECT post_title, post_content 
		FROM $wpdb->posts
		WHERE post_author = '%d' 
		AND post_type = '%s'
		",
		$page_author_id, // %d because it is number
		$post_type_name // %s because it is string
	)
);

// print the page titles to see the result
if( $pages ) {
	foreach ( $pages as $page ) {
		echo $page->post_title;
	}
}

Change Administrator ID

Before in this tutorial we changed administrator login. But what about the ID? Everyone knows that the main administrator is the user with ID=1.

So, I feel myself really uncomfortable when my administrator ID is 1. Many of the SQL-injections are based on this principle. So, let’s open phpMyAdmin and run the following queries (making backups before doing changes is highly recommended).

UPDATE wp_users SET ID = 5487 WHERE ID = 1;
UPDATE wp_posts SET post_author = 5487 WHERE post_author = 1;
UPDATE wp_comments SET user_id = 5487 WHERE user_id = 1;
UPDATE wp_usermeta SET user_id = 5487 WHERE user_id = 1;
ALTER TABLE wp_users AUTO_INCREMENT = 5488

The above code works pretty well for any WordPress. But for WordPress Multisite your have to duplicate lines 2,3 for each site of the network like this:

UPDATE wp_2_posts SET post_author = 5487 WHERE post_author = 1;
UPDATE wp_2_comments SET user_id = 5487 WHERE user_id = 1;

But what if you have hundreds, or even thouthands blogs in your multisite network? Well, I think in that case you could run the queries in the loop using PHP and $wpdb.

These changes won’t affect your superadmin permissions, which are connected to your login.

7. Files and Directories

Disable directory browsing

Try to open yourdomain/wp-content/plugins URL in your browser and this is something you shouldn’t see:

Disable plugins directory browsing in WordPress

Usually WordPress already includes empty index.php file in /wp-content/plugins//wp-content/themes/ and /wp-content/uploads. But what about all the other directories without index.php, even like /wp-content/uploads/2017?

Or course, sometimes your hosting provider handles this but if it doesn’t, then just open .htaccess in your site folder and add there the following:

Options -Indexes

Now instead of the directory listing a 403 Forbidden error will be displayed.

Allow only media files in uploads directory

# at first we completely disable access to all the files
<Files ~ ".*..*">
	Order Allow,Deny
	Deny from all
</Files>
# after that add file extensions you want to allow access
<FilesMatch ".(jpg|jpeg|jpe|gif|png|mp4|pdf)$">
	Order Deny,Allow
	Allow from all
</FilesMatch>

Or you can just disable PHP files (though it could be not enough).

Disallow file edits

Do you use Appearance > Editor or Plugins > Editor? If yes, you know that it allows you to edit PHP files in themes and plugins. It means if someone got your admin password, he can do everything he wants using this editor.

To disable it, insert to your wp-config.php the line below:

define( 'DISALLOW_FILE_EDIT', true );

The result:

A more strict way:

define( 'DISALLOW_FILE_MODS', true );

It doesn’t just disable your editor but also:

  • new plugins can not be added,
  • new themes can not be added,
  • but unfortunately it also disallows all existing plugin/theme/core updates.

8. Backups

It also can be described in a one simple sentence – if you do not have a backup, after an attack you may lose your website.

Do not trust to hosting backups. Always make it yourself. With a plugin or manually.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X