As the most widely used and the most vulnerable content management system (CMS) by far, WordPress and its core platform are still robust and secure among the other known popular blogging systems. But actually, three things can undermine the security of WordPress. The first one is poorly configured installation/deployment, the second one is third-party plugins and the last one is the lack of continuous maintenance and monitoring.
After reviewing the current guides about WordPress security, I have seen that there is no compact guide which comprehensively covers the most critical hardening tips without any plugins. In this article, I will try to give a comprehensive guide and step-by-step instructions for hardening and securing WordPress without relying on any plugin. All instructions I will give here should be applied sequentially and in a controlled manner. All of these will be represented from the information security perspective.
During this hardening and securing job, the most important thing is not to rely on any third-party plugins. Your hardening baseline must start with and rely on WordPress core platform settings. Then, the other supportive tools should be taken into account thereafter. Because when you hand over your initial hardening or securing job to a third-party plugin, you suddenly will be increasing the attack surface of WordPress. A third-party security plugin can malfunction or cause glitches contrary to as expected. As such, there is not a day that goes by without a vulnerability that affects third-party plugins.
The idea that the security of WordPress can be achieved with 3rd party plugins is a false sense of security. Unfortunately, sometimes so-called security experts recommend this. In the first place, we have to distinguish the job of securing WordPress into its internal and the other supportive tools. So, our top priority should be on a firm footing and we need a solid foundation in terms of security.
Hence, our focal point is the hardening of the core platform of WordPress without any third-party plugin. Before starting, I strongly advise you to get a backup of the current installation or htaccess files and follow all instructions step-by-step after making sure you do not see any problems. These instructions are mainly on .htaccess file editing. And the outline is as follows:
- Changing the “admin” User
- Changing WordPress Database Table Prefix
- Protecting Critical Files
- Securing wp-includes Folder
- Restricting Admin Access
- Forcing SSL for All Logins and wp-admin folder
- Setting New Security Keys
- Getting Theme and Plugin Updates Automatically
- Disabling WordPress Internal Code Editor
- Disabling Error Reporting
- Restricting Access to Theme and Plugins PHP Files
- Preventing Files Being Executed in Uploads Folder
- Disabling Malicious Script Injections
- Preventing Directory Listing
- Setting Proper File Permissions
- Removing Version Information
- Implementing Security Headers
- Preventing Username Enumeration
- Disabling Debug Mode Logs
- Disabling XML-RPC
- Stopping Spam Logins and Comments
Please note that the lines between # BEGIN WordPress
and # END WordPress
in the .htaccess file are dynamically generated via WordPress. Any changes to the lines between these markers will be overwritten. So, all snippets must be placed outside the # BEGIN WordPress
and # END WordPress
tags to ensure they are not overwritten by WordPress.
1. Changing the “admin” User
During the installation of WordPress, you must choose and assign different username instead of “admin”. It will make things hard for hackers. Assigning another account for content publishing beside your custom administrator username, reduces the attack surface and eliminate the exposing of your real administrator username in the archive or writer URL.
2. Changing WordPress Database Table Prefix
During the installation of WordPress, using custom table prefix will make WordPress more secure. Otherwise, the default installation will assign “wp_
” prefix automatically. If you already have a WordPress installation, it is possible to change it by just editing “$table_prefix
” parameter in the “wp-config.php” file below:
OLD ==> $table_prefix = 'wp_';
NEW ==> $table_prefix = '4rg6ks8e_';
3. Protecting Critical Files
It is crucial to protect critical files like wp-config.php, .htaccess, php.ini, error logs, htpasswd and so on with the code snippet below:
<FilesMatch "^.*(error_log|wp-config\.php|php.ini|\.[hH][tT][aApP].*)$">
Order deny,allow
Deny from all
</FilesMatch>
You can add more file types to the definition above by separating file types with a pipe ‘|’ sign.
4. Securing wp-includes Folder
WordPress core files reside in the /wp-includes/
folder where scripts and users are generally not required to access. So, nothing should reach there. Using the following snippet, you can make sure it does not happen:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
5. Restricting Admin Access
There are a few options to protect /wp-admin/
or wp-login.php
page. Adding an extra layer to this page or directory may significantly increase the overall security. And as I said in the beginning, I will not be offering or using any plugins in this first section.
The foremost requirement is to enable an HTTPS (SSL/TLS) connection for the administration so that all communication is encrypted. After enabling SSL connection, restricting IP access to wp-login.php
is the easiest and effective way. You may allow only required IP addresses (X.Y.Z.T or A.B.C.D is your IP address):
<Files wp-login.php>
order deny,allow
allow from X.Y.Z.T
allow from A.B.C.D
deny from all
</Files>
Another way is to add basic HTTP authentication. You can add this functionality by cPanel’s “Password Protect Directories” or the manual method. The cPanel’s settings do not let you set additional details but only setting of the password. The manual method is not burdensome and I think it is more convenient. For the manual method, you first need to create a .htpasswd file which can be generated by this htpasswd generator tool. Then you must put this file outside of your public web folder (not in /public_html/, /yourdomain.com/, /www/ or /webroot/) for extra security. After you upload the .htpasswd file, you have to tell .htaccess where it’s at. Then, you need to declare proper directives in a .htaccess file of /wp-admin/
directory as below:
AuthName "Protected Admin Panel"
AuthUserFile /home/yourpath/.htpasswds/
AuthType basic
require user yourusername
After enabling basic HTTP authentication, you may break the Ajax functionality where involved. I don’t recommend you do this but you can solve this problem by adding the snippet below to .htaccess file of /wp-admin/
directory.
<Files admin-ajax.php>
Order allow,deny
Allow from all
Satisfy any
</Files>
Also, some login page hiding plugins build a symbolic link to the original wp-login.php
. But they are useless. And doing it by code modification may break core functionality on upgrades.
6. Forcing SSL for All Logins and wp-admin folder
To ensure all login credentials and admin panel’s sensitive data are encrypted during transit, you must add the following rows to your wp-config.php
file:
define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_ADMIN', true);
7. Setting New Security Keys
WordPress authentication unique keys and salts are used to encrypt information stored in browser cookies so that protecting user passwords and other sensitive data. These secret keys are stored inside wp-config.php
and can be generated randomly from WordPress SALT tool. You must set your keys by using that tool:
define('AUTH_KEY', '@]`b|SC,Xnxm4jgT%NV3NF6h 9. w}s!g=cZyp_2~{SVi-|ba(&[n-MqZ0_4MOPs');
define('SECURE_AUTH_KEY', '--<rJ_~+X|{x7G g=f hfTDHd[Lz3T,`I4-%g[_ml}qGT(ieA+#R*dc}Wka/hx4d');
define('LOGGED_IN_KEY', '.L9r0V,]lNg^P(B];1a}%@Eb#Vj,2G+OI`b&-Em7gQ~X1jy@>(Ivyt.5qULlD%iL');
define('NONCE_KEY', 'Q->C9L -L(C`$*|:XwfM;XpO[06Ri4!J48qjuvL^f3f>C5Yu{M>egEhHvt/-|v,A');
define('AUTH_SALT', 'm?^k=P`|%38<iO3.e0b~2C_kizB`U&.d~MB>R^TY4GcI=&6 .S}v{{y2;l}geZ,d');
define('SECURE_AUTH_SALT', 'kJr:`9jQn-`CoH&a1^M+Iwu!-G*0@q/zZ6J7#.H%d^k%;k=AHqNP209r>(,BTh>1');
define('LOGGED_IN_SALT', 'k^-Q%/a[Tn+f<!ANIYzKf+wKoJ)36B-*-0j-<+n!#v[2)3yDzHO60BI)e*AAmG8f');
define('NONCE_SALT', 'u-nqlQK#;9tb#<*}<)G_2*vsl+;&pQGM9Hp6i+h@3)HatB3oP|-iM+T)=_>7(m_Y');
8. Getting Theme and Plugin Updates Automatically
According to the security best practices, all updates should be applied immediately. If a critical vulnerability emerges in the wild, someone always tries to use this vulnerability to gain access or attack your WordPress site. So, all plugins and theme’s updates should be applied automatically as soon as possible. You should not hesitate when applying any updates automatically, whether it brakes your functionality or not. Moreover, if your WordPress site is complicated and overcrowded with lots of plugins, it is possible to break your site’s functionality during an update. And if there is a complex or complicated website, it is clear that you do not manage your site properly and you have systemic risk. So, you need to simplify your website against plugins and to set automatic updates by adding the lines below in the wp-config.php
:
define('WP_AUTO_UPDATE_CORE', true);
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );
9. Disabling WordPress Internal Code Editor
WordPress has a built-in code editor for plugins and theme files. Though it can be useful and convenient, it facilitates attacker job with enabling files editing on your WordPress panel without gain access to your hosting server. It is critical to disable this code editor by adding the following line to your wp-config.php
file:
define('DISALLOW_FILE_EDIT', true);
10. Disabling Error Reporting
Due to exposing critical information about your website or server environment, it is important to switch off PHP error reporting in the wp-config.php
file:
error_reporting(0);
@ini_set(‘display_errors’, 0);
11. Restricting Access to Theme and Plugins PHP Files
To prevent any malicious code/content injection to PHP files of your plugins or theme, you should lock up them via the snippet below:
RewriteCond %{REQUEST_URI} !^/wp-content/plugins/file/to/exclude\.php
RewriteCond %{REQUEST_URI} !^/wp-content/plugins/directory/to/exclude/
RewriteRule wp-content/plugins/(.*\.php)$ - [R=404,L]
RewriteCond %{REQUEST_URI} !^/wp-content/themes/file/to/exclude\.php
RewriteCond %{REQUEST_URI} !^/wp-content/themes/directory/to/exclude/
RewriteRule wp-content/themes/(.*\.php)$ - [R=404,L]
12. Preventing Files Being Executed in Uploads Folder
The relatively vulnerable wp-content/uploads
folder could lead to the execution of some files like py, PHP, JSP and so on. It is vital to prevent such a malicious action with the line below:
RewriteRule ^wp-content/uploads/.*\.(php|pl|jsp|shtml|sh|inc|rb|py)$ - [F,L,NC]
13. Disabling Malicious Script Injections
Input form areas or executable files are try-out places for hackers to inject malicious things. There are some measures for this type of attack. Even if you have limited options to prevent injections without any supportive apps, plugins or layer, you can still block some type of injections to some extent with the snippet below:
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|[|%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|[|%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
The protection against <script>
tag is not enough because hackers use different versions of <script>
like <scr<script>ipt>
, <scrscriptipt>
and so on. So, you should adapt the third row of the snippet above according to your needs.
14. Preventing Directory Listing
Nobody should see your WordPress folder structure just by browsing the URL. It needs to be closed immediately with the line below in your main htaccess file:
Options All -Indexes
15. Setting Proper File Permissions
It is critical to set your WordPress folders and files permissions (chmod setting) like below (as a minimum):
- For directories: 750
- For files: 640
- For
wp-config.php
: 600
16. Removing Version Information
WordPress exposes version information that helps hackers during their attacks. It is very useful clue. And you should disable it in your theme’s function.php
file:
function remove_wordpress_version_number() {
return '';
}
add_filter('the_generator', 'remove_wordpress_version_number');
function remove_version_from_scripts( $src ) {
if ( strpos( $src, 'ver=' . get_bloginfo( 'version' ) ) )
$src = remove_query_arg( 'ver', $src );
return $src;
}
add_filter( 'style_loader_src', 'remove_version_from_scripts');
add_filter( 'script_loader_src', 'remove_version_from_scripts');
Or use this:
remove_action('wp_head', 'wp_generator');
17. Implementing Security Headers
Security headers can help a website to prevent some type of attacks and reduce the threat surface at the server and application level. They are not bulletproof and but are effective to some degree.
For Clickjacking Attacks
In your htaccess file, you can add the snippet below:
<IfModule mod_headers.c>
Header always append X-Frame-Options SAMEORIGIN
</IfModule>
Or in your theme’s functions.php
file, add the row below:
header(‘X-Frame-Options: SAMEORIGIN’);
For Drive-by-Downloads and XSS Attacks
In your htaccess file, you can add the snippet below:
<IfModule mod_headers.c>
Header set X-Content-Type-Options nosniff
Header set X-XSS-Protection "1; mode=block"
</IfModule>
Or in your theme’s functions.php
file, add the row below:
header('X-XSS-Protection: 1; mode=block');
header('X-Content-Type-Options: nosniff');
For Cross-Site Tracing Attack (HTTP Trace Method)
In your htaccess file, you can add the snippet below:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^TRACE
RewriteRule .* - [F]
</IfModule>
For Cookies with Secure and HTTPOnly Flags
In your htaccess file, you can add the snippet below:
<IfModule php5_module>
php_flag session.cookie_httponly on
php_flag session.cookie_secure On
php_flag session.use_only_cookies On
</IfModule>
Or in your theme’s functions.php
file, add the row below:
@ini_set('session.cookie_httponly', true);
@ini_set('session.cookie_secure', true);
@ini_set('session.use_only_cookies', true);
18. Preventing Username Enumeration
Hackers try to find out all users/username in your WordPress. For this task, they always run some scripts or tools to enumerate all users. To prevent username enumeration in WordPress, you should add the following rule to your .htaccess file:
RewriteCond %{QUERY_STRING} author=d
RewriteRule ^ /? [L,R=301]
Or you can use below:
RewriteEngine On
RewriteBase /
RewriteCond %{QUERY_STRING} (author=\d+) [NC]
RewriteRule .* - [F]
19. Disabling Debug Mode Logs
Debug mode logs might cause information disclosure about your website, theme or plugins. It should not be used in production mode. It must be disabled in your wp-config.php
file as follows:
define( 'WP_DEBUG', false );
20. Disabling XML-RPC
If you do not use XML-RPC functionality, you should immediately disable it with the following code in your .htaccess file:
<Files xmlrpc.php>
order deny,allow
deny from all
allow from X.Y.Z.T
</Files>
21. Stopping Spam Logins and Comments
Spambots attack directly and usually does not leave a referrer. And wp-comments-post.php
and wp-login.php
are very practical targets for them to attack. So, we need to stop them by using the snippet below to prevent anyone who isn’t submitting the login form from accessing it:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} .(wp-comments-post|wp-login)\.php*
RewriteCond %{HTTP_REFERER} !.*yourdomain.com.* [OR]
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule (.*) http://%{REMOTE_ADDR}/$1 [R=301,L]
</ifModule>
Final Thoughts
Securing WordPress is not as simple as it seems. There are lots of things that must be done and followed continuously. All instructions above do not give you a bulletproof WordPress but give a robust foundation without relying on any plugin. After that, you may require or use some useful and supportive plugins on top of this foundation. Of course, you should never stop continuous monitoring and maintenance either.
Useful Resources
- https://wordpress.org/support/article/hardening-wordpress/
- https://wordpress.org/support/category/security/
- https://wordpress.org/support/article/htaccess/
- https://wordpress.org/support/article/brute-force-attacks/
- https://www.owasp.org/index.php/OWASP_Wordpress_Security_Implementation_Guideline
- https://www.acunetix.com/websitesecurity/preventing-wordpress-hack/
- https://www.wpbeginner.com/wordpress-security/
- https://www.wordfence.com/learn/introduction-to-wordpress-security/
The featured image above by Kevin Phillips from Pixabay