Hardening checklists tell you what to configure. Verification tells you what is currently true — which matters more, because most compromised sites were hardened at some point and then drifted. Everything here is a check with an observable answer.
WordPress runs a large share of the web, which makes it the most automated target on the internet. Almost none of that attention is directed at you specifically: it is scanners walking address ranges, testing for known vulnerable plugin versions and weak logins.
That is good news, because generic attacks are defeated by generic defences. The question is whether yours are actually in place right now.
Table of contents
- Check what is exposed from outside
- Check the accounts and the login path
- Check file integrity and permissions
- Check updates, backups, and the restore you have never tested
- How this fits the rest of the stack
- FAQ
Check what is exposed from outside
Start where an attacker starts, with no credentials.
# Is the version being advertised?
curl -s https://example.com | grep -i 'meta name="generator"'
# Are readme and config-sample files reachable?
curl -sI https://example.com/readme.html | head -n 1
curl -sI https://example.com/wp-config-sample.php | head -n 1
# Is directory listing enabled anywhere?
curl -s https://example.com/wp-content/uploads/ | head -n 20
# Does the login form leak which usernames exist?
curl -s -d 'log=probablynotauser&pwd=x' https://example.com/wp-login.php | grep -io 'unknown username'
# Is the REST users endpoint enumerating accounts?
curl -s https://example.com/wp-json/wp/v2/users | head -c 300
# Is XML-RPC open?
curl -sI https://example.com/xmlrpc.php | head -n 1
What the answers mean:
- A generator tag publishes your exact version, which lets a scanner skip straight to known vulnerabilities for it. Remove it.
- A reachable readme.html does the same thing in a different way.
- Directory listing on uploads exposes every file, including ones you assumed were unlisted.
- A distinct unknown-username message confirms which accounts exist, turning a two-part guess into a one-part guess.
- The REST users endpoint lists author accounts by default. This is by design and it is still a gift to a brute-force script.
- XML-RPC allows many login attempts in a single request, which defeats naive rate limiting. Disable it unless something you use needs it.
Each of these individually is minor. Together they turn an untargeted scan into a targeted one.
Check the accounts and the login path
Weak authentication is how most compromises actually happen — ahead of plugin vulnerabilities, and far ahead of anything sophisticated.
- How many administrators are there? Every one is a full compromise if taken. Users, Administrator role — look for accounts belonging to a former developer, an agency, or a plugin that created one during setup.
- Is there an account called admin? Half the guess is already done. Create a new administrator, transfer content, delete the old one.
- Is two-factor authentication on for every administrator? This single control defeats essentially all credential-based attacks, and it is the highest-value item in this article.
- Is there rate limiting on the login form? Without it a script gets unlimited attempts.
- Do contributors have more capability than they need? An editor account that only writes posts should be an author.
A quick inventory over the database:
SELECT u.ID, u.user_login, u.user_email, u.user_registered
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value LIKE '%administrator%';
An administrator you cannot account for is an incident, not a tidy-up. Registered recently, with an unfamiliar email address, is the classic signature of a compromise that has already happened.
Check file integrity and permissions
Core files are published, so you can compare yours against the originals. Anything modified in core is either a very bad customisation or an injected backdoor.
# Compare core files against the official checksums
wp core verify-checksums
# The same for plugins
wp plugin verify-checksums --all
# Files changed in the last week -- a good post-incident question
find . -type f -mtime -7 -name '*.php' | head -50
# PHP files where they have no business being
find ./wp-content/uploads -name '*.php'
That last one deserves emphasis. There is no legitimate reason for a PHP file to be in the uploads directory, and finding one there is the single most reliable indicator of a compromise. Block execution in that directory regardless of whether you find anything.
Permissions:
# Directories 755, files 644, wp-config tighter
find . -type d ! -perm 755 | head -20
find . -type f ! -perm 644 | head -20
ls -l wp-config.php # 640 or 600, not 644 and never 777
Anything world-writable is a finding. A 777 directory is writable by every process on the machine, which on shared hosting includes other people’s compromised sites.
Also confirm that wp-config.php is not backed up somewhere reachable — wp-config.php.bak, wp-config.old, or a .zip of the site in the document root. Those serve as plain text and contain your database credentials.
Check updates, backups, and the restore you have never tested
Known vulnerabilities in outdated plugins are the second most common way in after weak credentials, and the window between disclosure and mass exploitation is measured in days.
wp core check-update
wp plugin list --update=available
wp theme list --update=available
# Plugins installed but switched off -- still on disk, still exploitable
wp plugin list --status=inactive
That last check surprises people. A deactivated plugin’s files are still present and still reachable by URL, so a vulnerability in one is exploitable whether or not it is switched on. Delete what you do not use rather than deactivating it.
Then the question that decides how bad a compromise is:
- Do backups exist? Confirm by looking at one, not by trusting a dashboard.
- Are they somewhere else? A backup on the same server is deleted by whoever deletes the server.
- How far back do they go? A compromise found on Friday may have started three weeks ago. Seven days of retention means every backup is already infected.
- Have you restored one? An untested backup is a hypothesis. Restore into a scratch environment and confirm the site actually comes up.
The retention question is the one people get wrong. Attackers frequently sit quietly for weeks before doing anything visible, so short retention leaves you with nothing clean to go back to.
Finally, check that PHP itself is supported. A site on an end-of-life PHP version receives no security patches, and no amount of plugin hygiene compensates.
How this fits the rest of the stack
Almost everything above is a state that drifts. A plugin is deactivated instead of deleted, a developer’s admin account outlives the contract, a backup job fails quietly for two months. Verification is worth repeating on a schedule precisely because configuration does not stay where you left it.
What makes that repeatable is being able to look without a chain of SSH and SFTP steps. RunxBuild’s managed WordPress puts a file manager in the dashboard — browse, edit, upload, unzip and download — and a database browser alongside it with tables, rows, SQL, export and import. So checking for a stray PHP file in uploads, or listing administrator accounts, is a couple of clicks rather than a session with two clients and a set of credentials. Plans start at $3/month, with autoscaling, custom domains and runtime logs. To see what WordPress plus the rest of your stack comes to, the RunxBuild hosting calculator lists each piece as its own line item.
Useful related references:
- Squarespace vs WordPress: The Question Behind the Question
- WordPress Maintenance Services: What You Are Actually Paying For
- WP-CLI: The Commands That Make WordPress Administration Bearable
- Services on RunxBuild
FAQ
How do I check if my WordPress site has been hacked?
Run wp core verify-checksums and wp plugin verify-checksums --all to compare your files against the published originals, look for recently modified PHP files, and search the uploads directory for any PHP file at all. An unexplained administrator account is also a strong signal — check the users table directly rather than trusting the dashboard list.
What is the single most valuable WordPress security measure?
Two-factor authentication on every administrator account. Weak or reused credentials are the most common way sites are compromised, ahead of plugin vulnerabilities, and a second factor defeats essentially all credential-based attacks regardless of how the password leaked.
Is it enough to deactivate a plugin I do not use?
No. A deactivated plugin’s files remain on disk and remain reachable by URL, so a vulnerability in one is still exploitable. Delete plugins and themes you are not using rather than switching them off, and check for inactive ones as part of any audit.
Should I disable XML-RPC?
Usually yes. It permits many authentication attempts in a single request, which defeats simple rate limiting, and it is a common brute-force and amplification vector. Check whether anything you rely on uses it first — some mobile apps and remote publishing tools do — and disable it otherwise.
How long should I keep WordPress backups?
Long enough to predate a compromise you have not noticed yet — thirty days or more rather than seven. Attackers often wait weeks before doing anything visible, so short retention can mean every available backup already contains the infection. Store them off the server and restore one occasionally to confirm it works.