WordPressPossible compromise

How to tell if your WordPress site was hacked: files, users and checksums

Check for the traces attackers usually leave: PHP files in the uploads folder, core or plugin files that differ from the official release, administrator accounts or SSH keys you didn't create, new scheduled tasks, and PHP starting shell commands. Clean results don't prove a site is safe, but any hit means you should isolate the site, keep the logs and rotate secrets before cleaning up.

This guide covers check 01: Authentication and access control; check 08: Observability and deployment in the Zenveus Production Readiness Standard.

For builders

What this means, in plain words

When someone breaks into a WordPress site, they usually leave something behind so they can get back in: a hidden PHP file, a changed core file, an extra admin account or a scheduled task. These checks look for those traces. If you find one, stop the site from being used, save the logs, and rebuild from clean copies rather than trying to patch the infected files.

A scoped repair request

Using an AI builder? Paste this

Use this prompt in Lovable, Cursor, Replit, or Claude Code with the relevant server files available.

Help me check my WordPress server for signs of compromise without changing anything yet. Give me read-only commands to verify core and plugin checksums, list PHP files in wp-content/uploads, find PHP and .htaccess files changed in the last 21 days outside the cache, list administrator accounts with registration dates, and list WordPress cron events. Explain what each result would mean. Do not delete, edit or execute any suspicious file.

The same failure may appear as

  • PHP files appear in wp-content/uploads
  • wp core verify-checksums reports modified files
  • An administrator account nobody recognises
  • The site redirects visitors or shows spam only to search engines

Find the failure layer

Run these checks before rewriting anything

Each check removes a class of causes. Keep the first failing result, its timestamp, and the production log beside it.

01

Verify core and plugin files

WP-CLI compares your WordPress core and wordpress.org plugin files with the official releases. Premium plugins and themes aren't covered, so compare those against a fresh download.

If this failsModified or unexpected files in core or a public plugin need investigation; a file that should not exist is a strong sign of tampering.
wp core verify-checksums
wp plugin verify-checksums --all
02

Look for PHP where it doesn't belong

The uploads folder should hold media, not code. List PHP files there, then search recent PHP files for patterns common in web shells.

If this failsCode in uploads or files using eval with decoded input are likely backdoors; copy them for analysis before removing anything.
find wp-content/uploads -type f -iname '*.php*'
grep -rlE 'eval\s*\((base64_decode|gzinflate|str_rot13)|\$_(POST|REQUEST)\[' wp-content --include='*.php'
03

Review accounts and keys

List administrators with their registration dates, and check the server's authorized SSH keys and any hosting-panel users.

If this failsAn admin or key nobody can account for means someone had write access; remove it only after recording when it appeared.
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
04

Check recent changes and scheduled tasks

List PHP and .htaccess files changed in the last few weeks and compare them with deploys and updates you know about. Review WordPress cron events and the server crontab.

If this failsChanges that match no deploy or update, or cron entries that download or run code, point to persistence.
find . -type f \( -name '*.php' -o -name '.htaccess' \) -newermt "$(date -d '-21 days' +%F)" -not -path './wp-content/cache/*'
wp cron event list

Ranked diagnosis

Common root causes, in the order we would test them

01

Unpatched plugin or theme

A known vulnerability in a plugin, theme or PHP itself lets an attacker write files without logging in.

02

Stolen or weak credentials

An admin, hosting-panel or SFTP password was reused, phished or never changed from a default.

03

Upload handling that allows code

A form or plugin accepts a file that the server later executes as PHP.

04

Leftover or abandoned code

Old plugins, test scripts or backups left in the web root give attackers an extra way in.

Step-by-step repair

How to fix this in your app

Work over SSH with WP-CLI in the site root. Take a full file and database copy for evidence before you change anything.

Production safety ruleNever disable access controls, expose service keys, or add wildcard CORS as a routine shortcut.

01

Contain before cleaning

Put the site in maintenance mode or behind an IP allowlist, and copy the web server, PHP and WordPress logs before they rotate. Cleaning first destroys the evidence of how they got in.

02

Replace code from known-good sources

Reinstall WordPress core and every plugin and theme from official downloads instead of editing infected files. Remove anything you can't match to a source.

03

Rotate every credential the site could read

Change admin, database, hosting, SFTP and API credentials, generate new WordPress salts, and remove unknown users and SSH keys.

04

Close the entry point

Update the vulnerable component or PHP version, and block PHP execution in the uploads folder.

Implementation example

Apache or LiteSpeed: stop PHP from running in uploads

# wp-content/uploads/.htaccess
<FilesMatch "\.(php|phtml|phar)$">
  Require all denied
</FilesMatch>

This blocks direct execution of uploaded code; it does not remove a backdoor that already exists elsewhere. On Nginx, add a location block for the uploads path that denies .php.

Prove the repair

How to check that the fix worked

Run these checks with synthetic data in your test environment, then repeat the relevant acceptance checks after deployment.

  • wp core verify-checksums and wp plugin verify-checksums --all report no modified files.
  • find wp-content/uploads -iname '*.php*' returns nothing unexpected.
  • The administrator list and SSH keys contain only known people.
  • A test PHP file placed in uploads returns 403 instead of running.

If the check still fails

If files or users reappear after cleanup, assume persistence you haven't found: check the database for injected options and posts, every cron entry, and the hosting account itself, or bring in someone to run a full incident review.

When the built-in AI fix makes it worse

Recover one reproducible failure.

Pause generated changes, restore a known working branch, and capture one failing request with its logs. Change one layer and rerun the allowed and denied checks before proceeding.

Engineering handoff

What Zenveus checks when the quick fix is not enough

We trace one production request through the complete path, isolate the failing boundary, and leave behind evidence your team can repeat.

Entry point

Which vulnerability or credential let the attacker in, and when.

Persistence

Backdoor files, users, keys, cron jobs and database changes.

Data impact

What the attacker could read or change, for notification decisions.

Clean rebuild

Known-good code, rotated secrets and the controls that keep it closed.

Typical repair pattern

A rebuilt site whose files match official releases and whose entry point is closed

Checksums pass, uploads hold no code, every credential is new, and the component that let the attacker in is patched or removed.

Evidence left behind
  • Root-cause note
  • Verified production check
  • Rollback and prevention steps

Before the next release

Prevent this failure from returning

Run the checksum and uploads checks after every update window.
Keep plugins, themes and PHP on supported, patched versions, and remove what you don't use.

Clear answers

Questions teams ask before they touch production

01If the checksums pass, is the site clean?

Not necessarily. Checksums only cover WordPress core and wordpress.org plugins. Attackers also hide code in uploads, themes, premium plugins, the database and cron, so run the other checks too.

02Can I just delete the suspicious files?

Copy them and the logs first. Deleting a backdoor without finding how it arrived usually means it comes back.

03Do I need to tell my users?

It depends on what data the site held and where your users are. If personal data may have been accessed, get advice on your notification duties promptly; some laws set short deadlines.

04How do I know the fix worked in my app?

Run the verification checks on this page after cleanup, then repeat them a week later. A returning file or user means the entry point is still open.

Free next step

Check the boundary before you hand it over

The free tool helps you inspect this symptom. Its result does not establish whether the whole app is production ready.

Take it further

Fixed this one? Find the next one first.

This fix solves one symptom. A guided self-assessment walks you through the rest of the app, one session at a time, and ends with a report and a ranked fix list you can act on or hand to a developer.

Planning more than one stage? Founder Bundles combine the guided products from $149 and save up to $405.

The Verdict

Know whether the symptom is contained or structural.

We can see the symptom from here. What we cannot tell you from outside is whether it is contained or structural. A scanner collects evidence. A named senior engineer makes the decision. For $299, a named senior engineer reads your code and signs a written Verdict against the nine checks in the Zenveus Production Readiness Standard. The 48-hour clock begins when the required access and context are available. If the report does not give your developer a list they can act on, you do not pay.

The 48-hour clock starts when the required access and context are available.

Scroll to Top

Optional tracking stays off until you enable it. You can change your choice at any time.

Essential — always active
Security, requested features, and remembering these choices.

Read our cookie policy