A spiking Exim queue is one of those early warning signs that something on a cPanel server has gone sideways. Sometimes it is a compromised account blasting out phishing mail. Sometimes it is a legitimate client running a poorly throttled newsletter. Sometimes it is a contact form with no captcha that a bot has discovered. Whatever the cause, the longer the queue grows, the closer you get to landing on Spamhaus, SORBS, or one of the dozens of regional blocklists that will quietly tank deliverability for every account on the box.
Knowing the queue is hot is easy. Figuring out which cPanel account is responsible is the part that trips people up, because the sender address in the queue rarely tells the whole story. A single cPanel account can host fifty domains. A compromised script under one user can spoof headers to look like mail from another. And authenticated SMTP shows yet another identity entirely.
This post walks through the commands I reach for when I need to pin queue abuse on a specific cPanel account, fast.
Start With a Pulse Check
Before anything else, get a sense of scale:
exim -bpc
That gives you the raw count of messages in the queue. On a healthy shared server with a few hundred accounts, you might see anywhere from a few dozen to a few hundred frozen or deferred messages at any given time. If you are staring at 20,000, something is actively wrong.
For a quick top-down view:
exim -bp | exiqsumm | head -30
exiqsumm summarizes the queue by destination domain, which is great for spotting outbound abuse patterns. If the top recipients are hotmail.com, outlook.com, yahoo.com, and aol.com with thousands of messages each, you are almost certainly looking at a spam outbreak rather than a legitimate mailing list.
Top Senders by Envelope Address
The fastest way to identify candidates is to bucket the queue by sender:
exim -bpr | grep "<" | grep -v "<>" | awk '{print $4}' | sort | uniq -c | sort -rn | head -20
Breaking this down: exim -bpr lists the queue in reverse without doing a fresh DNS lookup (much faster on large queues), the first grep keeps only lines containing the envelope sender, the second grep filters out bounces (which have <> as the sender), and the rest is standard count-and-sort.
You will get output like this:
8423 <[email protected]>
2187 <[email protected]>
412 <[email protected]>
That top sender is your prime suspect. But which cPanel account owns clientdomain.com?
Mapping Domains to cPanel Accounts
cPanel maintains /etc/userdomains, which is a flat mapping of every domain on the server (primary, addon, parked, subdomain) to the cPanel user that owns it. To resolve a sender domain to an account:
grep -i "clientdomain.com:" /etc/userdomains
Output:
clientdomain.com: bobsmith
So clientdomain.com belongs to the cPanel user bobsmith. You can also chain this into a one-liner that turns the top-senders list directly into top cPanel users:
exim -bpr | grep "<" | grep -v "<>" | awk -F'[<>@]' '{print $3}' | \
while read domain; do
grep -i "^${domain}:" /etc/userdomains
done | awk '{print $2}' | sort | uniq -c | sort -rn | head -20
That walks every queued message, extracts the sender domain, looks up the owning cPanel user, and counts. The result is an aggregated, ranked list of cPanel accounts by queued message volume. On a server with addon domains, this is more accurate than ranking by domain alone, because a single user with twenty addon domains will otherwise look like twenty unrelated senders.
Why Sender Address Is Not Enough
Here is where it gets interesting. The envelope sender can be forged. A PHP script running as user alice can absolutely send mail with a From: [email protected] header. So if the visible sender does not match what your /etc/userdomains lookup expects, do not stop there. You need to look at where the mail actually originated.
There are two paths mail takes out of a cPanel server that matter here:
- PHP scripts calling
sendmail(most web app spam falls into this category) - Authenticated SMTP (typically a compromised mailbox password)
Each leaves a different signature in /var/log/exim_mainlog.
Catching PHP Script Abusers via cwd
When a PHP script invokes sendmail, Exim logs the script’s current working directory with a cwd= field. This is gold, because the cwd path almost always starts with /home/USERNAME/, telling you exactly which cPanel account is hosting the offending script.
grep "cwd=" /var/log/exim_mainlog | awk '{for(i=1;i<=NF;i++) if($i~/^cwd=/) print $i}' | \
sort | uniq -c | sort -rn | head -20
Output looks like:
14203 cwd=/home/bobsmith/public_html/wp-content/uploads
3211 cwd=/home/alice/public_html
847 cwd=/home/charlie/public_html/contact
That first line is a dead giveaway. A WordPress uploads directory should never be the origin of 14,000 mail sends. That is a backdoor PHP file dropped by an attacker, almost certainly through a vulnerable plugin or theme.
To roll this up to just the username:
grep "cwd=" /var/log/exim_mainlog | awk '{for(i=1;i<=NF;i++) if($i~/^cwd=\/home\//) print $i}' | \
awk -F'/' '{print $3}' | sort | uniq -c | sort -rn | head -20
You can also filter to recent activity (last hour, say) by piping through tail first or using a time-bounded search if your log rotation is aggressive.
Catching Authenticated SMTP Abusers
When mail is sent via authenticated SMTP, Exim logs an A=dovecot_login: or A=courier_login: field along with the auth user (almost always a full email address). To find the top auth senders:
grep "A=dovecot_login:" /var/log/exim_mainlog | \
awk '{for(i=1;i<=NF;i++) if($i~/^A=dovecot_login:/) print $i}' | \
sort | uniq -c | sort -rn | head -20
Output:
9821 A=dovecot_login:[email protected]
304 A=dovecot_login:[email protected]
That first address sent nearly 10,000 messages via SMTP auth. Either the mailbox password was leaked or guessed, or the client genuinely is running a newsletter without telling you. Either way, you now have an exact account to act on.
To map that email address back to a cPanel user, the email’s domain goes through /etc/userdomains the same way:
grep -i "clientdomain.com:" /etc/userdomains
Putting It Together
Here is a compact script I keep around for triage. It prints the top 10 cPanel accounts by queued mail volume, the top 10 cwd directories generating mail in the live log, and the top 10 SMTP auth users:
#!/bin/bash
echo "=== Top cPanel accounts in queue (by sender domain) ==="
exim -bpr | grep "<" | grep -v "<>" | awk -F'[<>@]' '{print $3}' | \
while read domain; do
grep -i "^${domain}:" /etc/userdomains 2>/dev/null
done | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Top cwd directories in exim_mainlog ==="
grep "cwd=" /var/log/exim_mainlog | \
awk '{for(i=1;i<=NF;i++) if($i~/^cwd=\/home\//) print $i}' | \
sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Top SMTP auth senders ==="
grep -E "A=(dovecot|courier)_login:" /var/log/exim_mainlog | \
awk '{for(i=1;i<=NF;i++) if($i~/^A=/) print $i}' | \
sort | uniq -c | sort -rn | head -10
Drop it in /root/bin/queue-abusers.sh, chmod it, and you have a one-command snapshot of who is doing what.
What to Do Once You Have Identified the Abuser
Identifying the account is the first half. The follow-up depends on what you found:
If it is a PHP script (cwd hit): freeze the queue for that user, suspend the account temporarily with whmapi1 suspendacct user=USERNAME reason="mail abuse investigation", then go looking for recently modified PHP files. A quick find like find /home/USERNAME/public_html -name "*.php" -mtime -7 -ls will surface recently touched scripts. Common culprits are obfuscated payloads in wp-content/uploads, theme files, or random directories with names like lv.php or wp-cache.php.
If it is SMTP auth abuse: change the mailbox password immediately via WHM or uapi --user=USERNAME Email passwd_pop_with_terms, then check /var/log/exim_mainlog for the source IPs. If the auth attempts came from a single IP or a handful in one country, the password was likely brute forced or leaked. If they came from hundreds of IPs, you are looking at a credential stuffing run with a known-good password, and the user may have reused it elsewhere.
To freeze and clean the queue for one user (sender domain example.com):
exiqgrep -i -f "@example.com" | xargs exim -Mf # freeze
exiqgrep -i -f "@example.com" | xargs exim -Mrm # remove
Be careful with the remove command. Once those messages are gone, they are gone. I almost always freeze first, confirm volume and content with exim -Mvh MSGID and exim -Mvb MSGID on a sample, and only then remove in bulk.
Prevention Worth Mentioning
Once you have stabilized the box, this is a good moment to revisit per-account hourly mail limits in WHM (Tweak Settings > Mail), enforce SMTP auth-only sending where possible, and make sure cPHulk or csf is actually catching repeated SMTP auth failures. Imunify360 (or whatever your malware scanner of choice is) should be running with real-time scanning on /home. None of these will catch every outbreak, but they shrink the window between compromise and detection from days to minutes, which is the difference between a contained incident and a server-wide blocklisting event.
The queue is going to fill up again eventually. The goal is to make sure that next time, you can name the abuser in under sixty seconds.