Skip to content
CH SCShop classdrupal · advanced · ~150 min · 7 steps

How to clean up a hacked Drupal site

Contain the damage, find how they got in, remove what they left behind, and rebuild trust in a compromised Drupal site step by step.

August 10, 2026 · by Dane Petersen

Strange admin accounts, spam pages in the search results, files you didn’t put there — if that’s your morning, take a breath. A hacked site is recoverable, but the order of operations matters: contain, preserve, investigate, clean, then reopen. Skipping ahead to “just delete the weird files” is how sites get re-hacked the same week. You’ll need SSH access, drush, a couple of quiet hours, and a backup you’ve actually tested.

Take the site offline politely

First, stop the bleeding. Maintenance mode keeps visitors out of the application:

drush state:set system.maintenance_mode 1
drush cache:rebuild

But understand its limits — maintenance mode is Drupal being polite, and malware doesn’t knock. If the compromise is in the code itself, return a real 503 at the web-server level (your host’s dashboard, or a temporary .htaccess rule) so PHP stops executing for the public entirely.

Preserve the evidence before you clean

The instinct is to scrub. Resist it for ten minutes. Copy the files, the database, and every log you can reach to somewhere off the server:

tar -czf ~/incident-$(date +%Y-%m-%d)-files.tar.gz /path/to/site
drush sql:dump --gzip --result-file=~/incident-$(date +%Y-%m-%d).sql

This copy is your map of what happened — and if the site handles payments or personal data, it may be evidence you’re obligated to keep. Never restore from it; it’s contaminated by definition.

Find the way in

Cleaning without knowing the entry point is bailing a canoe without finding the leak. Check the usual doors: an unpatched security advisory (cross-check your versions against drupal.org/security — this is why timely updates matter), a stolen password, or a writable spot in sites/default/files executing PHP. Look for accounts you didn’t make, especially privileged ones:

drush user:information --uid=1
drush sql:query "SELECT u.name, u.mail FROM users_field_data u"

Then read the access logs around each suspicious file’s modification time. POST requests to odd paths just before a file appeared is usually your answer.

Compare the code against known-good

If the site is in Git, this is one command:

git status

Anything modified or untracked is either yours or theirs — sort every file into one pile or the other, no exceptions. For core, contrib, and vendor code, don’t clean by hand: delete those directories and let composer rebuild them from upstream, which guarantees byte-for-byte clean copies. Custom code and the files directory you review line by line, searching for the classics:

grep -rn "eval(base64_decode" web/sites/default/files web/modules/custom

Rotate every credential

Assume they read everything. Change all admin passwords, the database password, $settings['hash_salt'] in settings.php (this invalidates one-time login links they may have minted), SSH and SFTP credentials, and any API keys the site holds. Do this after the code is clean — rotating secrets a backdoor can still read is just telling them the new ones.

Patch the hole and restore clean code

Now close the door you found in step three: apply the security update, remove the abandoned module, or fix the file permissions. Deploy the cleaned code, run drush updatedb -y and drush cache:rebuild, verify the site works while still behind the 503, then reopen:

drush state:set system.maintenance_mode 0
drush cache:rebuild

Watch the logs for their return

You’re not done at reopening — you’re done two weeks later. Attackers retry the same door, so tail the access logs for the paths and IPs from your investigation, watch for new accounts and file changes, and request recrawls in Search Console if spam pages got indexed. Success looks like this: two quiet weeks of logs, git status still clean, no accounts you didn’t create. Write up what happened and how they got in while it’s fresh — that document is the difference between an incident and a lesson.

That's the lesson. Back to the shop for more — or if this is the chore your organization never gets to,that's literally what we're for.