Skip to content
Ramesh Gautam

A WordPress security checklist for small sites

A WordPress security checklist for small sites, a WordPress article by Ramesh Gautam

Small WordPress sites rarely get targeted by a person. They get found by automated scanners trying the same weaknesses on millions of sites. That’s good news, because closing those few gaps blocks almost all of it. This WordPress security checklist is the one I ran on this site, in rough order of impact.

Updates and leftovers

  • Keep core, plugins and themes updated. Most compromises use a known vulnerability in an outdated plugin.
  • Delete what you don’t use. Deactivated plugins can still be exploited if their files are reachable. The same goes for old test installs in subfolders like /dev or /staging, which nobody updates and everyone forgets.
  • Check for files that shouldn’t exist. There should be no .php files in wp-content/uploads. WP-CLI’s wp core verify-checksums confirms core files are untouched.

Logins

  • No admin username. It’s the first one bots try. WordPress doesn’t let you rename a user in the admin, but you can change user_login in the database or create a new administrator and delete the old one.
  • Long, unique passwords from a password manager.
  • Two-factor authentication. The free Two Factor plugin, built by WordPress contributors, supports authenticator apps and backup codes. Save the backup codes before you need them.
  • Hide whether a username exists. WordPress login errors say “unknown username” or “incorrect password”, which confirms valid accounts. A filter makes it generic:
add_filter( 'login_errors', fn() => 'The login details are incorrect.' );

Stop leaking usernames

Even with a custom username, WordPress shows it in a few places:

  • The REST API lists users at /wp-json/wp/v2/users. Remove those routes for visitors who aren’t logged in (the block editor still needs them):
add_filter( 'rest_endpoints', function ( $endpoints ) {
	if ( is_user_logged_in() ) {
		return $endpoints;
	}
	foreach ( array_keys( $endpoints ) as $route ) {
		if ( str_starts_with( $route, '/wp/v2/users' ) ) {
			unset( $endpoints[ $route ] );
		}
	}
	return $endpoints;
} );
  • Author archive URLs use user_nicename. Set it to something that isn’t the login name.

Server settings

In wp-config.php, turn off the theme and plugin editor so a stolen login can’t edit PHP:

define( 'DISALLOW_FILE_EDIT', true );

In .htaccess, add security headers:

Header always set Strict-Transport-Security "max-age=31536000"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"

Also block files that reveal versions and paths: readme.html, license.txt, plugin readme.txt files and stray error_log files. Block XML-RPC if nothing uses it; many hosts already do.

Backups

Security also means recovery. Keep automatic backups of the files and database, store copies off the server, and test a restore at least once. A backup you’ve never restored is a hope, not a plan.

In your code

If you write themes or plugins, escape everything you output and check capabilities and nonces on every action. I covered the output side in escaping output in WordPress themes.