Learn / Hardening WordPress, Step by Step

Hardening WordPress, Step by Step

GUIDE

7 min read · 1,474 words

None of these steps is difficult. Together they remove most of the easy ways in.

None of these steps is difficult. Most take a few minutes, and almost all of them are free. Together they remove the easy ways in, which matters because the overwhelming majority of WordPress compromises are not clever. They are an old plugin with a known flaw, a password reused from somewhere else, or a file left writable that should not have been.

Hardening does not make a site unbreakable, and anyone who says it does is selling something. What it does is move you out of the crowd of sites that automated scanners can take over without effort. Attackers run the same scripts against thousands of sites an hour; they move on when a site does not respond to the usual tricks.

The steps below are grouped by layer and ordered roughly by payoff. Work through them once, tick the checklist at the end, and put a reminder in your calendar to repeat the checks every quarter.

Accounts: unique logins, two-factor, least privilege Software: core, plugins and themes updated, nothing unused Configuration: wp-config.php, file editing off, XML-RPC, login limits Server: current PHP, permissions, no PHP in uploads, HTTPS Watching: logs, scans, alerts Backups kept outside the account: the layer that saves you when the others fail
Each layer stops a different class of attack; none of them is enough alone.

Accounts

Most login attacks are guesses against the username admin, using passwords leaked from other sites. Take away the first and the second becomes useless.

Software

Outdated plugins are the main way in. The flaw is published, the exploit is shared, and scanners start probing within hours. The fix is dull: update promptly.

You can list pending updates from the command line: wp plugin list --update=available. It takes seconds, so make it a habit.

Configuration

These changes happen in wp-config.php and a few server files. Take a backup of each file before editing.

Disable the built-in theme and plugin editor. If an attacker gets into the dashboard, the editor lets them write PHP straight into your site:

define('DISALLOW_FILE_EDIT', true);

There is a stronger setting, DISALLOW_FILE_MODS, which also blocks installing and updating from the dashboard. Use it only if you handle updates some other way, otherwise you will stop receiving them.

Protect wp-config.php, because it holds your database password. WordPress will load it from one directory above the web root, so on hosts that allow it, move it there. Otherwise deny web access to it and set its permissions to 640 or 600.

Refresh the authentication keys and salts if you suspect a breach. Changing them logs everyone out, which is the point: wp config shuffle-salts does it in one step.

Disable XML-RPC if nothing you use needs it. The file xmlrpc.php accepts many login attempts in a single request, which makes it a favourite for guessing passwords. With Apache 2.4:

<Files xmlrpc.php>
  Require all denied
</Files>

Some mobile apps and a few plugins use XML-RPC, so test the things you rely on afterwards. Finally, limit login attempts, with a plugin or at server level with a tool such as fail2ban that watches the log for repeated failures and blocks the address for a while.

Server

Run a supported PHP version. PHP 8.x releases get security fixes for a limited period, then stop; anything older gets none. Check what you run with php -v or the hosting panel, and check that your plugins support the next version before upgrading.

Set file permissions to 755 for folders and 644 for files, with wp-config.php tighter. The files should be owned by your account's user and not be writable by "everyone". From the site root:

find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 600 wp-config.php

Block PHP execution in the uploads folder. Uploads should only ever hold images and documents; a PHP file there is almost always an attacker's backdoor. For Apache, put this in wp-content/uploads/.htaccess:

<FilesMatch "\.php$">
  Require all denied
</FilesMatch>

On nginx the equivalent is a location ~* /wp-content/uploads/.*\.php$ { deny all; } block in the server configuration. Use HTTPS everywhere: install a certificate, redirect HTTP to HTTPS, and add define('FORCE_SSL_ADMIN', true); so login and admin traffic is never sent in plain text.

Before GET /uploads/shell.php PHP runs: attacker in After GET /uploads/shell.php 403 Forbidden: file inert Illustrative: the file name is an example
One short rule turns an uploaded backdoor into a harmless file.

What changes by hosting type

On shared hosting, the provider handles the operating system, server software and account isolation, so your job is the application: accounts, plugins, configuration. You can usually set PHP version and permissions from the panel. On a VPS or dedicated server, the operating system, firewall, SSH and database are also yours; keep packages patched, allow SSH by key only, and open only the ports you use. On managed WordPress hosting, many of the server items above are done for you, but accounts and plugins remain your responsibility.

Watching

Hardening reduces the chance of an intrusion; watching tells you when one gets through. Keep a log of administrator logins and file changes. Run a periodic malware scan, and get alerts when a new administrator appears, since adding one is the first thing many intruders do.

Core files can be compared against the official release: wp core verify-checksums and wp plugin verify-checksums --all report any file that differs. To see what changed recently: find . -name '*.php' -mtime -3. Backups belong outside the hosting account, at another provider or in storage the site's login cannot reach, and a backup you have never restored is a hope rather than a backup.

If something goes wrong

Do not start deleting files at random. Take the site offline or behind a password, change every password (WordPress, database, hosting panel, FTP), and refresh the salts. Then decide: if you know exactly what happened, clean it; if you do not, restore from a clean backup taken before the problem began and apply updates before putting the site back. The troubleshooting guide and the stories in the casebook show what real clean-ups look like.

Commands worth running

Settings you have not tested are settings you hope are working. Ten minutes with a terminal tells you where you stand.

A worked example: a small business site had eleven plugins, three of them deactivated, and five administrators, two of whom had left the company. Deleting the dormant plugins, removing the two accounts and turning on two-factor took about twenty minutes. Nothing visible changed, which is how it should feel. Three months later one of the deleted plugins turned out to have a published flaw, and the site was not exposed to it.

Checklist