Removing malware from WordPress should be straightforward: scan, quarantine, delete. In practice you'll hit file permission errors, reinfection loops, and cleanup tools that fail halfway through. I've walked dozens of site owners through these problems in support tickets, and the same seven errors come up every time.
This guide covers each error with its symptoms, root cause, and the exact fix. You'll see real commands and file paths, not vague advice.
Error 1: Permission denied during file deletion
Your malware scanner identifies infected files, but when you try to delete them you get "Permission denied" or "Operation not permitted." The scan log shows the bad files, but rm or your FTP client refuses to remove them.
Why it happens
Malware often sets restrictive ownership or file attributes to protect itself. The infected file might be owned by a system user like nobody or have immutable flags set with chattr +i. Your hosting account user can't touch it.
The fix
First, check file ownership and permissions:
ls -la /home/username/public_html/wp-content/uploads/malicious.php
If the owner is wrong (not your cPanel user), you need root access or a support ticket. On a VPS where you have root:
sudo chown username:username /path/to/infected/file
sudo rm /path/to/infected/file
Check for immutable attributes:
lsattr /path/to/infected/file
If you see an i flag, remove it:
sudo chattr -i /path/to/infected/file
rm /path/to/infected/file
On shared hosting without shell access, open a ticket with your host. Provide the full file paths from your scan log. They can reset ownership in seconds.
Error 2: Malware reappears after cleanup
You delete every infected file, run a clean scan, then check back an hour later and the malware is back. Same files, same locations. Sometimes you see new malicious files with random names in wp-content/uploads.
Why it happens
A backdoor is still active somewhere. Could be a web shell hidden in a plugin, a compromised admin account, or a cron job that rewrites infected files. The initial cleanup removed symptoms, not the entry point.
The fix
Start by hunting for hidden backdoors in uncommon locations:
find /home/username/public_html -name "*.php" -mtime -1
This shows PHP files modified in the last day. Look for files in odd places: root directory, wp-content/uploads, inside image folders.
Check active cron jobs:
crontab -l
Malware sometimes adds a cron entry that re-downloads a payload. If you see an unfamiliar wget or curl command, remove it:
crontab -e
# Delete the suspicious line, save and exit
Audit WordPress users in the database:
mysql -u dbuser -p dbname -e "SELECT user_login, user_email FROM wp_users;"
Look for admin accounts you don't recognize. Delete them through phpMyAdmin or wp-cli:
wp user delete 123 --allow-root
Finally, reset all WordPress passwords and salts. Generate new salts from the WordPress salt generator and update wp-config.php.
Error 3: Scanner times out on large sites
Your malware scanner starts, runs for five minutes, then stops with "Maximum execution time exceeded" or "504 Gateway Timeout." The scan log is incomplete. You can't get a full picture of the infection.
Why it happens
PHP's max_execution_time limit kills long-running scripts. On a site with 50,000 files or massive plugins like WooCommerce, a deep scan takes longer than the default 30 or 60 seconds. The web-based scanner dies before finishing.
The fix
Increase the execution time in .htaccess if you're on Apache:
php_value max_execution_time 300
php_value memory_limit 512M
Or in a custom php.ini in your document root:
max_execution_time = 300
memory_limit = 512M
If that doesn't work (shared hosting sometimes ignores local PHP settings), run the scanner from the command line instead. Most security plugins offer a CLI scan:
wp wordfence scan --allow-root
CLI scans bypass web server timeouts entirely. They can run for hours if needed.
For huge sites, scan directories in chunks. Point your scanner at wp-content/plugins first, then wp-content/themes, then wp-content/uploads. Takes longer but you get complete results.
Error 4: Database tables still contain malicious scripts
Files are clean, but malware keeps appearing in post content or widget areas. You see <script> tags injecting spam links or redirects. Removing them manually doesn't stick—they reappear on page load.
Why it happens
Malware injects JavaScript or iframes directly into the database. Common targets: wp_posts.post_content, wp_options.option_value (for widgets and theme settings), and wp_postmeta. Your file scanner never touched the database, so the injection persists.
The fix
Search the database for common malware patterns:
mysql -u dbuser -p dbname -e "SELECT * FROM wp_posts WHERE post_content LIKE '%<script%' OR post_content LIKE '%<iframe%';"
If you find infected rows, note the ID and clean them with a query:
UPDATE wp_posts
SET post_content = REPLACE(post_content, '<script src="http://malicious.site/bad.js"></script>', '')
WHERE ID = 456;
Check the wp_options table for injected widgets:
mysql -u dbuser -p dbname -e "SELECT option_name, option_value FROM wp_options WHERE option_value LIKE '%<script%';"
Widgets are serialized, so you'll see encoded HTML. Easiest fix: delete the suspicious option row and reconfigure your widgets through the WordPress admin.
For widespread database infections, use a plugin like Better Search Replace to strip malicious patterns across all tables. Back up first—search-replace can break serialized data if you're not careful.
Error 5: Clean install still triggers security warnings
You've replaced every core file with a fresh WordPress download, updated all plugins, and scanned clean. But your security plugin or Google Safe Browsing still flags the site as compromised.
Why it happens
Two possibilities. First, you missed a backdoor in the server environment outside the WordPress directory—a malicious cron job, a rogue .htaccess rule in the parent directory, or an infected system library. Second, you cleaned the site but external blacklists haven't updated yet. Google's crawl delay can be days.
The fix
Check .htaccess files above your document root:
cat /home/username/.htaccess
cat /home/username/public_html/.htaccess
Look for auto_prepend_file or auto_append_file directives that load a PHP file before every request. That's a common persistence trick:
php_value auto_prepend_file /tmp/backdoor.php
If you find one, delete the referenced file and remove the directive.
Check system-wide cron:
sudo cat /etc/crontab
sudo ls /etc/cron.d/
On shared hosting, ask support to verify no global cron jobs are reinfecting your account.
If the site is actually clean, request a manual review from Google Search Console under Security Issues. Provide evidence: recent malware scan results, proof of file replacement, log snippets. Delisting usually takes 72 hours.
Error 6: Plugin or theme won't update after cleanup
You try to update an old plugin that was the infection vector, but WordPress throws "Could not copy file" or "Destination folder already exists." The update hangs at 50%. You're stuck on a vulnerable version.
Why it happens
Malware modified plugin files directly, changing file sizes or checksums. WordPress's update routine detects a mismatch and refuses to overwrite. Or leftover malicious files inside the plugin directory block the update unpacking process.
The fix
Delete the plugin directory entirely via SSH or FTP, then reinstall:
rm -rf /home/username/public_html/wp-content/plugins/vulnerable-plugin
Then in WordPress admin, go to Plugins > Add New, search for the plugin, and install fresh. Don't use the "Update" button—it tries to preserve existing files.
If you need to keep plugin settings, back up the database first. Most plugins store settings in wp_options with a predictable key name. After reinstalling, your settings should persist.
For themes, same approach:
rm -rf /home/username/public_html/wp-content/themes/compromised-theme
Upload a clean copy via Appearance > Themes > Add New. Never try to patch an infected theme file by file—you'll miss something.
Error 7: Server still sends spam emails after cleanup
WordPress looks clean, but your server keeps sending spam. Your host suspends outbound email or blacklists your IP. Mail logs show thousands of messages you didn't authorize.
Why it happens
A PHP mailer script sits outside WordPress—often in a hidden directory like /tmp, /var/tmp, or inside a writable cache folder. It sends spam independently of WordPress. You cleaned the CMS but not the underlying server.
The fix
Check common writable directories for PHP files:
find /tmp /var/tmp -name "*.php" -ls
find /home/username/public_html -type d -writable -exec find {} -name "*.php" \;
Look for files with names like mailer.php, sender.php, temp123.php. Delete anything suspicious:
rm /tmp/mailer.php
Check recent mail logs for the sending script's path:
grep "X-PHP-Originating-Script" /var/log/exim_mainlog | tail -50
You'll see a header showing which PHP file sent the email. Track it down and remove it.
Harden file permissions so malware can't write to system temp directories:
sudo chmod 1777 /tmp
The sticky bit (1) prevents users from deleting each other's files but still allows world-writable access. On shared hosting, ask your provider to lock down temp directories.
Finally, disable PHP functions commonly abused by mailers. In php.ini or via MultiPHP INI Editor:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
What to check first when cleanup fails
Before you dive into advanced troubleshooting, verify these basics. A huge percentage of malware removal failures come down to one of these oversights.
Make sure your backup is actually clean. If you restore from a backup that already contained malware, you're right back where you started. Check the backup date against your earliest evidence of infection.
Confirm all passwords are changed—WordPress admin, database user, FTP, cPanel, SSH. Malware often steals credentials and uses them to re-enter after you clean up.
Disable all caching. Page caches and object caches can serve old infected content even after you've cleaned the files. Clear all caches (server-level, plugin-level, CDN) and turn caching off until you're sure the site is clean.
Run a second scanner from a different vendor. One scanner's signatures might miss a new malware variant. If Wordfence finds nothing, try Sucuri or Malcare. Cross-check.
What if I don't have SSH access?
Use File Manager in cPanel for file operations. You can view file permissions, ownership, and attributes. For tasks requiring root (like chattr or chown), open a ticket with your host. Provide specific file paths and explain what you need changed.
Some security plugins offer one-click malware removal through their dashboard. They handle permissions internally. Not as thorough as manual cleanup, but better than nothing.
Can I skip the database cleanup?
Not if you want a durable fix. File-only cleanup leaves database injections intact. You'll see symptoms disappear briefly, then return as WordPress re-renders pages using infected database content. Always scan the database after cleaning files.
How do I know if the cleanup actually worked?
Run a full scan with two different tools. Check Google Safe Browsing status for your domain. Monitor your server's outbound email count—if it's still spiking, something's wrong. Watch file modification times: if PHP files in wp-content/uploads keep changing, you missed a backdoor.
Preventing the next infection
Once you've cleaned the site, lock it down. Update WordPress core, all plugins, and themes to the latest versions. Remove any plugins you're not actively using—inactive plugins still get exploited.
Set correct file permissions:
find /home/username/public_html -type f -exec chmod 644 {} \;
find /home/username/public_html -type d -exec chmod 755 {} \;
chmod 600 wp-config.php
Enable a web application firewall. Cloudflare's free tier blocks common exploit attempts. If you're on a VPS, install ModSecurity.
Schedule automatic backups to a remote location. UpdraftPlus or Jetpack can push daily snapshots to cloud storage. When (not if) the next attack happens, you'll have a known-good restore point.
Monitor file changes with a plugin like Sucuri or Wordfence. They'll alert you within minutes of a new infection, not days later when a customer reports spam.
