Skip to content
  • FG-25
  • 13 min read
  • Last checked November 8, 2026

Hosting Account Suspended for Malware, Phishing or Spam? How to Clean Up and Get Reinstated

Your host suspended the account over malware, phishing or outgoing spam. Decode the abuse notice, find the mailer script or stolen mailbox, clean every site on the account and get reinstated.

Run the health check

Your site shows an “Account Suspended” page or a 403 error, email has stopped sending, and your inbox holds a message from your host saying the account was suspended for malware, phishing, spam or an abuse complaint. Sometimes only part of the account is disabled: outgoing mail is blocked, or one folder is locked, while the site still loads.

Hosts suspend accounts to protect everyone else on the same servers. One account sending spam can get the server’s IP address blocklisted, so mail from every customer on it starts bouncing. One phishing page can bring legal complaints to the host. The suspension is not a verdict on you; it is a pause until you prove the problem is gone. This guide covers reading the notice, finding the source (especially of outgoing spam, which the other guides do not cover), checking every site on the account, and writing a reinstatement request that gets accepted. For general cleanup, see FG-05; for phishing kits and blocklists, FG-24.

What is happening

A suspension starts with a trigger, and the trigger tells you where to look:

Trigger Usually means What the notice usually includes
Phishing complaint from a brand, a security company or a national CERT A phishing kit in a subfolder One or more full URLs, the reporting party, a date
Host’s malware scanner Backdoors, injected code or uploaded scripts A list of file paths and signature names
Outbound mail volume or spam complaints A PHP mailer script, an abused contact form, or a stolen mailbox password Message counts, a sample spam message or its headers, sometimes a blocklist listing
Abnormal CPU or processes Malicious processes such as crypto miners or attack scripts run from the account Process names, paths, resource graphs

Outgoing spam has two very different sources. A script on the website calls PHP’s mail function or connects to the mail server directly. A compromised mailbox means someone logged in with a real email account’s password and is sending through your mail server like a normal email program. The fixes are completely different, so you must tell them apart.

Before you start

  • Ask for access. Many hosts leave the control panel or SFTP working during a suspension, or will restore access for cleanup if you ask. Reply to the notice and ask.
  • Do not move hosts to escape it. The malware, the backdoors and the attacker’s access move with your files. Clean first.
  • Use a clean device and change your hosting panel password before anything else.
  • Back up the files, databases and mail as they are, and store the copy off the server as evidence.
  • Where commands run: find, grep and wp run in an SSH terminal on the server; start in your home folder (cd ~) or a site folder as each step says. Some hosts call WP-CLI wp-cli. Mail server commands (exim, postqueue) need root on your own VPS or dedicated server; on shared hosting, ask your host for the equivalent output. Words in CAPITALS are placeholders explained under each command.

Step 1: Read the abuse notice properly

Abuse notices are terse and often forwarded from someone else. Pull out these facts and write them down:

  • Reference or ticket number and any deadline.
  • Category: phishing, malware, spam, resource abuse.
  • Evidence: URLs, file paths, a sample spam message, or headers.
  • Timestamps. Notices usually use UTC. Convert them to the server’s time zone before matching them with logs.
  • What the host requires to lift the suspension: removal of listed files, a full cleanup, a scan report, or proof of a professional cleanup.

If the notice includes a forwarded spam message, its headers are the best clue you will get. Look for these lines:

X-PHP-Originating-Script: USER_ID:FILE_NAME.php
Received: from USERNAME by SERVER_NAME with local (Exim)
Received: from [IP_ADDRESS] by SERVER_NAME with esmtpsa ... (Exim) (envelope-from <MAILBOX@example.com>)

These are example header lines to look for, not commands. USER_ID is the numeric system user that ran the script, FILE_NAME.php the script that called PHP’s mail function, USERNAME your hosting account’s system user, SERVER_NAME your host’s mail server, IP_ADDRESS the computer that connected, and MAILBOX@example.com the sending address. An X-PHP-Originating-Script header (added when the server enables PHP’s mail.add_x_header setting) or a with local line means a script sent it. A with esmtpsa or esmtpa line from an outside IP, with the final a meaning authenticated, means someone logged in to a mailbox to send it. Header details vary by mail server, so if yours look different, send them to your host and ask which case applies.

Step 2: Find the source of outgoing spam

Check the mail queue and logs (VPS or dedicated server)

If you have root on an Exim server (common with cPanel), start with the queue:

exim -bpc
exim -bp | head -40
exim -Mvh MESSAGE_ID

The first command prints the number of messages waiting in the queue. A few is normal; thousands means active spam or a delivery problem. The second lists the first 40 queued messages with their age, size, message ID, sender and recipients. The third shows the full headers of one message: replace MESSAGE_ID with an ID from the list (it looks like three groups of letters and digits joined by dashes). Read the headers as in Step 1 to tell script from mailbox.

Then ask the log which folders and which logins sent the most mail:

grep "cwd=" /var/log/exim_mainlog | grep -v "/var/spool" | grep -oE "cwd=[^ ]+" | sort | uniq -c | sort -rn | head
grep -oE "A=dovecot_(login|plain):[^ ]+" /var/log/exim_mainlog | sort | uniq -c | sort -rn | head

Type both as shown (the log path is the cPanel default; use zgrep and the rotated file name for older days). The first counts locally submitted messages by the working folder the sending process ran in, which points straight at the website folder whose script sent mail; grep -v "/var/spool" removes Exim’s own queue runs. The second counts messages by authenticated mailbox login. sort | uniq -c | sort -rn | head counts identical lines and shows the top ten, busiest first.

Good output: small counts spread across your normal mailboxes and your site’s folder. Bad output: thousands of messages from one site folder, such as a folder inside wp-content/uploads, which means a script; or thousands from one mailbox login, which means a stolen password. On Postfix servers the log is usually /var/log/mail.log (Debian and Ubuntu) or /var/log/maillog (RHEL family), postqueue -p lists the queue and postcat -q QUEUE_ID shows one message, replacing QUEUE_ID with an ID from that list.

On shared hosting, look for your control panel’s email delivery tracking tool, which shows recent outgoing messages and their senders, and ask your host for the relevant log lines.

If a script is sending: find it

Mailer scripts are small PHP files that accept a list of recipients and a message from the attacker and pass them to PHP’s mail function or an SMTP library. Search places where scripts should never be:

grep -rlE "mail\s*\(|PHPMailer|fsockopen|smtp" --include=*.php wp-content/uploads
find . -name "*.php" -ctime -DAYS -not -path "./wp-content/cache/*" -exec ls -l {} \;

Run both from the site folder. In the first, -r searches subfolders, -l lists only file names, -E enables the pattern, and the pattern matches calls to mail(, the PHPMailer library, raw socket connections and SMTP code. Any PHP file in uploads that matches is almost certainly a mailer. In the second, replace DAYS with a number that reaches back before the spam started; it lists PHP files whose disk entry changed in that window. Look for random file names, names that imitate WordPress files (such as wp-options.php in the wrong folder), and files outside plugin and theme folders.

If the sender is a legitimate file, the culprit may be an abused form instead. Contact forms that send a copy to the address the visitor types, or that let the visitor set the subject freely, can be used to relay spam with no hack at all. Check your form plugin’s entries or email log for a flood of submissions, and add a CAPTCHA or turn off “send a copy to the sender”.

If a mailbox is sending: lock it down

  1. Change the password of the mailbox the log or headers named, to a long unique one, from a clean device.
  2. Check that mailbox’s forwarders, filters and autoresponders for anything you did not set up; attackers add forwarders to keep reading mail after a password change.
  3. Check the devices that use that mailbox for malware, and update the password everywhere it is saved.
  4. Change the passwords of every other mailbox on the domain if you cannot rule out reuse.
  5. If the website sends mail through an SMTP plugin, change that account’s password too and update the plugin.

Clear the spam that is still queued

On your own server, count the spam before you remove it, then remove only that sender’s messages:

exiqgrep -c -f "SENDER_ADDRESS"
exiqgrep -i -f "SENDER_ADDRESS" | xargs exim -Mrm

Replace SENDER_ADDRESS with the address or part of an address the spam uses, as shown in the queue list. -f matches the sender, -c prints only a count, and -i prints only message IDs, which xargs passes to exim -Mrm to delete them. The second line permanently deletes every queued message from that sender, so check the count first and make sure the pattern cannot match a real sender. Then check whether the server’s IP was blocklisted using a multi-list checker such as MXToolbox, and follow each list’s delisting process once the spam has stopped.

Step 3: Clean the malware or phishing content

Start with every file the host listed, but do not stop there: the host’s scanner found what it recognized, not everything the attacker left. Follow FG-05 for the full sequence (rogue admins, checksums, backdoors, rebuild from clean sources, password rotation), and FG-24 for finding phishing kits. Keep a list of every file you remove, with its path and creation time; you will need it for the reinstatement request.

Find the entry point too. Hosts often ask how the attacker got in, and a request that names it is far more convincing than one that only lists deleted files.

Step 4: Check every site in the account

On most hosting plans, every site in one account runs as the same system user, so a script in one site can read and write every other site. Attackers know this and spread to all of them. A suspension caused by one site is not lifted for long if a second, forgotten site still holds a backdoor.

List every WordPress install in your account:

find ~ -maxdepth 5 -name wp-config.php -not -path "*/backup*" 2>/dev/null

Type it as shown. ~ is your home folder, -maxdepth 5 limits how deep it searches so it finishes quickly, the -not -path part skips folders with “backup” in their path, and 2>/dev/null hides permission errors. Each line is one install. Expect your main site, addon domains and subdomains; watch for old test copies, /old/ folders and staging sites you had forgotten. Those are often the original entry point.

Then check all of them at once:

for d in $(find ~ -maxdepth 5 -name wp-config.php -printf '%h\n' 2>/dev/null); do echo "== $d"; wp --path="$d" core verify-checksums --include-root; find "$d/wp-content/uploads" -name "*.php" 2>/dev/null; done

Type it as shown. The loop takes each folder that contains a wp-config.php (-printf '%h\n' prints the folder rather than the file), prints a heading, compares WordPress core in that folder with official checksums using --path to point WP-CLI at it, then lists any PHP files in its uploads folder. Good output for each site is the Success line and nothing from uploads. Anything else needs the full cleanup. Do not forget non-WordPress apps and static folders in the account: run the grep mailer search from Step 2 against the whole home folder too.

If an old site cannot be cleaned or is no longer needed, delete it after taking an off-server copy. Every unmaintained install is a liability.

Step 5: Write a reinstatement request that gets accepted

Hosts reinstate accounts when they believe the cause is understood and fixed. Vague promises get rejected; specifics get accepted. Reply to the original notice so it stays on the same ticket, and include:

Reference: TICKET_NUMBER
Cause: SHORT_DESCRIPTION (for example, mailer script uploaded through PLUGIN_NAME version VERSION)
Entry point and fix: HOW_THEY_GOT_IN and WHAT_YOU_CHANGED
Removed: list of FILE_PATHS, plus rogue admin USERNAME
Other sites checked: list of SITE_FOLDERS, results
Credentials rotated: hosting panel, SFTP, database, all mailboxes, WordPress admins, salts
Mail: queue cleared on YYYY-MM-DD, no further sends from the script or mailbox
Prevention: for example, PHP blocked in uploads, unused plugins removed, two-factor on admins
Request: please unsuspend, or re-enable access so you can rescan

This is a template for your ticket reply, not a command. Replace TICKET_NUMBER with the reference from the notice, the descriptions in CAPITALS with your findings, FILE_PATHS with the files you removed, USERNAME with any rogue account you deleted, SITE_FOLDERS with the installs from Step 4, and YYYY-MM-DD with the date. Leave out lines that do not apply rather than inventing an answer.

What gets requests rejected: deleting only the listed files and claiming the job is done; saying it was a false positive without evidence; restoring an old backup without closing the hole; and resuming mass mailing straight after reinstatement. If the host asks for a third-party scan or a professional cleanup report, provide it. If outgoing mail was disabled separately, ask for it to be re-enabled once you have shown the source is fixed, and expect hourly sending limits for a while.

Advanced: tracing a mailer from the spam header back to the entry point

You can often reconstruct the whole attack from one header. Take the file name from X-PHP-Originating-Script or the folder from the cwd= log lines, then find the file and its timestamps:

find ~ -name "FILE_NAME.php" -exec stat -c "%n | modified %y | changed %z" {} \;

Replace FILE_NAME with the script name. stat -c prints a custom line: the path (%n), the modified time (%y) and the change time (%z). The modified time can be faked; the change time records when the file actually appeared or was altered on this disk. Now search your access logs around that change time for the request that created it:

grep "FILE_NAME.php" ACCESS_LOG | head -5
grep "IP_ADDRESS" ACCESS_LOG | grep "POST" | head -20

Replace ACCESS_LOG with the path to your site’s access log (use zgrep for .gz files). The first line shows the earliest requests to the mailer, each starting with the IP address of the client that called it. Replace IP_ADDRESS in the second line with one of those addresses: it lists that client’s POST requests, and the one just before the file’s change time, often to a plugin file or admin-ajax.php, is usually the upload. That plugin and endpoint are your entry point. Name them in your reinstatement request.

On your own server, you can make this easier next time by setting PHP’s mail.log directive to a file path. PHP then logs every call to its mail function, including the full path of the calling script, so a future mailer shows up in one file.

When to contact your host

You are already in contact, so keep it on one ticket and keep replies factual. Ask early for:

  • Temporary access to clean, if the suspension blocks it.
  • The full evidence: the complaint, file list, sample message headers and mail log lines with UTC timestamps.
  • Whether the server IP was blocklisted, and whether they will request delisting once you are clean.
  • Exactly what they need to see before reinstating.

If the account is suspended again after reinstatement, the attacker still has a foothold. Work through FG-26 before you ask for a second reinstatement, because hosts are much less patient the second time.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close