Skip to content

How to remove malware from a hacked WordPress site

Take the site offline, keep a copy of it as it is, and change every password and the secret keys. Then restore a backup from before the break-in, or replace WordPress and every plugin with clean copies and check what is left. Finish by finding and closing the way in.

By
WP Ministry
Updated

In short

  • Lock the site down first. Take it offline, keep a copy of the hacked state, and change every password and the secret keys.
  • A backup from before the break-in is usually a surer route than cleaning by hand.
  • Replace WordPress, plugins and themes with fresh copies. Do not repair infected files one at a time.
  • Checksums cover WordPress and the plugins from WordPress.org. They say nothing about uploads, custom code or the database.
  • The site is not clean until you know how they got in and have closed it.
  • If the infection comes back, a backdoor was missed. That is the point to get help.

A hacked site pushes you to do something at once. WordPress's own advice starts in the opposite place: stay calm, and write down what you are seeing. The work has an order. Lock the site down, clean it, then find the way in. A site that is cleaned while the way in stays open can be broken into again the same way.

If you have a backup from before the break-in, read "Restore from a clean backup instead" further down before you clean anything by hand.

Confirm that the site was hacked

These are the signs WordPress's documentation treats as clear:

  • A search engine or a browser shows a warning in place of your site.
  • Your host has disabled the site, or tells you it is sending spam or attacking other sites.
  • Visitors say their antivirus flags your pages.
  • Something happened that nobody on your side did, such as a new user appearing.
  • You can see it: pages show content you did not put there, or send visitors somewhere else.

Not every broken site is a hacked one. A blank page straight after an update points to a plugin conflict, not to a break-in.

Write down what you see, when you first saw it, and what changed on the site recently: a new plugin, an edit to the theme, a new user. Then ask your host. They may be able to confirm whether it is a hack at all, and whether other sites on the same account are affected.

A scanner shows more of the extent. One that reads the site from outside sees what visitors see, and a scanner plugin reads the files on the server. Neither finds everything. WordPress security plugins compared covers the plugin kind.

Lock the site down

  1. Step 1: Take the site offline

    Ask your host to take the site offline for visitors, or to limit it to your own IP address, so that nobody is served infected pages while you work. WordPress's own maintenance mode is no use for this: it ends by itself after ten minutes. A maintenance page from a plugin keeps visitors off your pages, but it does not stop a file the attacker planted from being opened directly.

  2. Step 2: Keep a copy of the site as it is

    Before you change anything, download every file and export the database. The copy is your record of what was done to the site, and it is what you go back to if the cleanup removes something you needed. Label it as infected, keep it off the server, and never restore it over a working site. Ask your host for the access logs now as well, before the older ones are gone. With WP-CLI, this command exports the database to a file in your home folder. It goes there, and not into the site's folder, because a file in the site's folder can be downloaded by anyone who guesses its name. Download the file and delete it from the server.

    bash
    wp db export ~/hacked-state.sql
  3. Step 3: Change every password

    Change the hosting account, SFTP and SSH, the database, and every WordPress user who can log in to the dashboard, administrators first. Do it from a computer you have scanned for malware, because a password stolen from your own machine will be stolen again. After changing the database password at your host, put the new one in wp-config.php as DB_PASSWORD, or the site will show an error establishing a database connection.

  4. Step 4: Replace the secret keys

    Replacing the secret keys in wp-config.php logs out everyone who is logged in, the attacker included. Without WP-CLI, WordPress.org runs a key generator that returns a fresh set of eight lines, and WordPress's FAQ for hacked sites links to it. Paste the new lines over the eight matching ones in wp-config.php.

    bash
    wp config shuffle-salts

Replace everything that has a clean original

Do not try to repair infected files. Anything that has an original gets deleted and put back from the original. Delete first, because copying fresh files over the top replaces the files that were changed and leaves the files that were added.

  1. Step 1: Check WordPress against its checksums

    The first command prints the version you are running. Write it down. The second compares every WordPress file with the checksums WordPress.org holds for that version, and warns about anything in the site's main folder that is not part of WordPress. Some of those files will be yours. Open each one you do not recognize.

    bash
    wp core version
    wp core verify-checksums --include-root
  2. Step 2: Replace WordPress with clean files

    Delete the wp-admin and wp-includes folders. Nothing of yours is in them. Then download the same version again, using the number you wrote down in place of 1.2.3, and run the check from the first step once more. --skip-content leaves out the default themes and plugins, so wp-content is not touched, and wp-config.php is not part of the download. Without WP-CLI, download the same version from WordPress.org, unzip it, and upload wp-admin, wp-includes and the loose files beside them over SFTP.

    bash
    wp core download --version=1.2.3 --force --skip-content
  3. Step 3: Check and reinstall every plugin

    The command below compares each plugin with WordPress.org's checksums. A plugin bought elsewhere cannot be checked this way. What fails tells you where the attacker has been.

    After it has run, reinstall all of them, whatever the result. List them with wp plugin list --skip-plugins --skip-themes, delete each plugin's folder, and install a fresh copy from where it came from. For a plugin from WordPress.org that is wp plugin install plugin-folder-name --skip-plugins --skip-themes. A paid plugin comes from its author's own download page. Leave out any plugin you do not use or do not recognize. Look in wp-content/mu-plugins too: if neither you nor your host put a file there, treat it as suspect.

    bash
    wp plugin verify-checksums --all --skip-plugins --skip-themes
  4. Step 4: Do the same for themes

    Delete the themes you do not use and reinstall the active one from its source. For a custom theme or a child theme, the only clean original is your own copy: the developer's repository or the files you were first given. Without one, read every file. Injected code tends to sit where it runs on every page, such as functions.php, header.php and footer.php.

  5. Step 5: Look for PHP files in uploads

    WordPress keeps your media in wp-content/uploads, and the web server can write to that folder, which makes it a convenient place to hide a file. Run this from the site's main folder and open each file it lists. Some plugins keep small files of their own there. A file that no plugin of yours accounts for, or one filled with long unreadable strings, should come off the server.

    bash
    find wp-content/uploads -type f -name "*.php"
  6. Step 6: Check the administrator accounts

    List the administrators and delete any you did not create. wp user delete 123 --reassign=1 --skip-plugins --skip-themes removes user 123 and gives their posts to user 1.

    bash
    wp user list --role=administrator --skip-plugins --skip-themes
  7. Step 7: Read .htaccess and wp-config.php

    Compare .htaccess with the standard block WordPress writes, which is shown in how to fix the 403 Forbidden error. Rules that send visitors elsewhere, and rules that neither you nor a plugin added, come out. Look for other .htaccess files in subfolders too. Compare wp-config.php with wp-config-sample.php from a fresh download. Yours should differ only in the database details, the keys, the table prefix and settings that you, your host or a plugin added. Trace any line that loads another file before you trust it.

Restore from a clean backup instead

If you have a backup from before the break-in, restoring it is usually the better route. A restore puts back every file and the database as they were, so you do not have to find each thing the attacker changed. It replaces the file-by-file work above. Locking the site down and finding the way in still apply.

  • The backup has to be older than the break-in, not older than the day you noticed. The first sign can come well after the first change. If you are unsure, go further back, and run the checksum commands above on what you restored.
  • Restore into an empty folder. Delete the site's files first. You have the copy. Restoring over the top leaves planted files where they are.
  • Whatever was added since the backup is gone. That means orders, comments, form entries and new posts. Content can be copied across by hand from the hacked copy. Files should not be, apart from media you have looked at.
  • The hole is restored too. The backup holds the same out-of-date plugin, the old passwords and the old keys. Straight after restoring, change the passwords and the keys again and update everything.

Find how they got in

Look for the way in before the site goes back online.

  • Out-of-date software. Once a fix for a hole is released, the hole is public, which leaves old versions more open to attack. Look in each plugin's changelog for a security fix you had not installed, then update everything. How to safely update WordPress has the order.
  • A stolen or guessed password. Malware on your own computer can capture what you type. Scan every computer used to manage the site, and turn on two-factor authentication for every administrator.
  • A plugin or theme from an untrusted source. A copy of a paid plugin that did not come from its author is not to be trusted. Remove it and get the plugin from the author.
  • Another site on the same hosting account. A cleaned site beside an infected one may not stay clean. Every site on the account needs the same work.
  • The access logs. Around the time of the first change, look for requests to files that should not exist.

If you cannot find it, the way in may still be open. Watch the site and scan it again over the following weeks.

When the site is clean, change every password and the secret keys once more, because the first change was made while the attacker may still have had access. Then put the site back online. The WordPress security guide covers what to tighten next.

Ask search engines to review the site

If Google has flagged the site, visitors see a warning in search results or a full-page warning in the browser. Cleaning the site does not remove it. You have to ask for a review.

In Google Search Console, open the Security issues report. If the site is not in Search Console yet, add it first. The report lists what Google found, with sample addresses. Fix the problem across the whole site, not only on the samples, then select "Request Review" and describe what you fixed. Google decides whether to lift the warning, and when.

Bing and the antivirus companies keep their own lists, each with its own request form. If your host suspended the account, ask which files they flagged, and when the site is clean send them the same description.

What a cleanup you do yourself can miss

The checks above show that WordPress and the plugins from WordPress.org match their originals. They show nothing about:

  • the database, where injected scripts and spam links can sit inside posts and settings;
  • a custom theme or plugin, when you have no original to compare it with;
  • paid plugins, which WordPress.org holds no checksums for;
  • the uploads folder, beyond the PHP files you looked for;
  • anything on the hosting account outside WordPress, including other sites.

A backdoor can be one file, or a few lines inside a real one. Miss it and the attacker comes back without needing the original hole.

Stop and get help when:

  • the infection returns after you have cleaned the site;
  • you find code you cannot read and have no clean original to replace it with;
  • you have no backup from before the break-in and the site runs custom code;
  • the site holds customer accounts, orders or payment details;
  • you cannot find how they got in.

Our malware removal service is a one-time clean-up with hardening, a blacklist removal request and a written report, and if the infection comes back within 30 days we clean the site again. Scanning, updates and backups from then on are what a care plan does, described under WordPress security.

Common questions

Should I delete everything and start again?

Starting again still needs your content, and the content lives in the database, which is one of the places an infection can sit. A fresh install with the old database imported is not clean merely because its files are new. The practical form of starting again is restoring a backup from before the break-in. Without one, replace every file that has an original and have the database checked.

A scanner says the site is clean. Is it?

It means that scanner found nothing it recognizes. WordPress's own advice is that no single scanner is the best approach and that using more than one improves the odds. Treat a clean scan as one piece of evidence, beside the checksums and a quiet few weeks.

Do I need to tell my customers?

If the site holds accounts, orders or form entries, assume the attacker could read them. Anyone who could change your files could also read the database password in wp-config.php. Reset your users' passwords. Whether you must notify people or a regulator depends on the law where you and your customers are, so ask someone qualified, and ask early.

More on this subject

Malware removal, done for you

Malware removal is $99. Full clean-up, hardening, blacklist removal request and a written report. 30-day re-clean guarantee. It starts with a free diagnosis.