exim -bp out of habit. On a plain Postfix box that command does nothing, exigrep is gone too, and there's no WHM screen to tell you which account is sending. The question you're really asking hasn't changed, though: which mailbox logged in and handed all this mail to the server? Postfix records that as clearly as Exim did. It just puts it in a different place.
This is a common moment for admins who moved off cPanel. The Exim tools did a lot of correlation for you, and Postfix spreads the same job across a few small commands. Once you see how they fit together, the hunt usually takes minutes.
Confirming the queue is full of outbound spam
postqueue -p prints the same thing as mailq: one block per message with the queue ID, size, arrival time, envelope sender, and recipients. A * after the queue ID means the message is in the active queue right now. A ! means it's on hold. Everything else is waiting in the deferred queue for another delivery attempt. The last line gives the total size and message count.
BASH
# Total count and size (last line of the listing)
postqueue -p | tail -n 1
# Rank envelope senders in the queue
postqueue -p | awk '/^[0-9A-Za-z]+[*!]? / {print $7}' | sort | uniq -c | sort -rn | head
# Sender domains in the deferred queue, grouped by age (qshape may need an extra package)
qshape -s deferred | headMAIL FROM, and spammers using a stolen login often set it to something random or to another address on your domain. What it does tell you is whether this is one flood or a mix. If the queue is so full that Postfix starts reporting write errors, check the inode situation first, as covered in How to Check If a Stuck Mail Queue Ran Your Server Out of Inodes.
Pick two or three queue IDs from the top of the flood and keep them handy. They're the thread you'll pull for the rest of the investigation.
Mapping your Exim habits to Postfix
exigrep did. In Postfix, the queue ID is the join key. Every daemon that touches a message logs a line starting with that ID, so a plain grep on the mail log rebuilds the whole story.
| On cPanel Exim | On Postfix | What changes |
|---|---|---|
exim -bp | postqueue -p or mailq | Similar listing, different column layout |
exim -Mvh ID / exim -Mvb ID | postcat -q ID | Shows envelope, headers, and body of the queue file together |
exigrep ID /var/log/exim_mainlog | grep ID on the mail log | No grouping tool. The queue ID links the lines |
exiqsumm | qshape | Groups by domain and age instead of a flat summary |
A=dovecot_login:user in exim_mainlog | sasl_username= on the smtpd line | Same fact, logged once when the message is accepted |
exim -Mrm ID | postsuper -d ID | Permanent deletion. Hold first if unsure |
A=dovecot_login out of exim_mainlog. On Postfix the equivalent is sasl_username. SASL is the authentication layer Postfix uses for SMTP AUTH, usually backed by Dovecot.
Reading one message with postcat
postcat -q ID | less. You're checking that it really is spam and not a legitimate newsletter that hit a rate limit. You'll also see the envelope sender and recipients, the headers the client sent, and the Received header Postfix added when it accepted the message, which includes the client's IP.
Don't expect the authenticated username in the headers. Postfix only adds an (Authenticated sender: ...) note to the Received header if smtpd_sasl_authenticated_header is enabled, and that's off by default because it exposes login names to every recipient. Run postconf smtpd_sasl_authenticated_header to see your setting. Whatever it says, the mail log is the reliable source.
Tracing the queue ID back to a login
sendmail binary go through a different path and show up as pickup lines instead. The two look like this:
TEXT
Illustrative output format:
postfix/submission/smtpd[2101]: 3F1A2B4C5D: client=unknown[203.0.113.50], sasl_method=LOGIN, [email protected]
postfix/cleanup[2105]: 3F1A2B4C5D: message-id=<[email protected]>
postfix/qmgr[987]: 3F1A2B4C5D: from=<[email protected]>, size=2048, nrcpt=50 (queue active)
postfix/pickup[1200]: 9E8D7C6B5A: uid=33 from=<www-data>[email protected]) differs from the login ([email protected]). That gap is exactly why the queue listing alone misleads you. Grep your sample IDs, then rank every authenticated sender in the log:
BASH
# Log path depends on distro: /var/log/mail.log (Debian/Ubuntu) or /var/log/maillog (RHEL family)
grep '3F1A2B4C5D' /var/log/mail.log
# Rank authenticated users by number of accepted messages
grep -o 'sasl_username=[^ ,]*' /var/log/mail.log | sort | uniq -c | sort -rn | head
# Which client IPs logged in as the suspect user
grep '[email protected]' /var/log/mail.log | grep -o 'client=[^,]*' | sort | uniq -c | sort -rn | headjournalctl for the Postfix unit into the same grep commands. The unit name varies by distro, so check systemctl list-units | grep postfix first. A compromised mailbox usually stands out right away: one username far ahead of the rest, often logging in from many unrelated IPs. That spread is typical when stolen credentials are handed around a botnet, and it's also a hint that blocking a single IP won't hold.
Red herrings that send you the wrong way
sasl_username line and they all trace back to pickup, no mailbox was compromised. A local process submitted the mail, and the uid tells you which system user ran it. On web servers that's often the PHP user, which points to a vulnerable script or plugin. Changing mailbox passwords won't help here. You need to find and remove the script.
A queue full of messages with from=<> is a different problem. The empty sender means bounces, so your server is generating non-delivery reports, often in response to inbound spam sent to addresses that don't exist. It looks like an outbound flood in mailq, but it's a recipient validation issue.
Finally, ignore the From: header in the message body. It's set by the client and easy to forge. The sasl_username field is what Postfix verified against a real password, so it's the one to trust.
Stopping the account and confirming it worked
- Reset the mailbox password wherever your SASL backend reads it (Dovecot's user database, LDAP, or SQL, depending on the setup). Then ask the owner to check their devices, since a reused or phished password will just get stolen again.
- Put the spam on hold instead of deleting it: feed the matching queue IDs to
postsuper -h -, which reads IDs from standard input. Held mail stops retrying but stays on disk, so you can still inspect it and release anything legitimate withpostsuper -H. - Once you've reviewed the held messages, delete them with
postsuper -d -. Deletion is permanent and can't be undone. Double-check your ID list before running it, and avoidpostsuper -d ALLunless you really mean to drop every message on the server. - Don't flush the queue to clear the backlog. Pushing spam out faster only damages your IP reputation further. Should You Run postqueue -f or Let Postfix Retry Deferred Mail on Its Own? covers why.
sasl_username lines for it should stop, or turn into authentication failures in the Dovecot or smtpd logs. Clients that were already logged in may keep their session until they disconnect. If you still see accepted mail from the account after the reset, block the client IPs at the firewall while their connections drain. Re-run the queue count from the first section a little later. It should be falling, and new arrivals should no longer come from the same login.
The habit to carry over from cPanel is the reasoning, not the commands. Find the queue ID, follow it in the log to the smtpd line, and trust sasl_username over anything the message claims about itself. Keep the ranking one-liner somewhere easy to reach. The next time the queue spikes, it answers the key question before you've opened a single message.