Short answer: Exim’s main log is /var/log/exim_mainlog on cPanel and /var/log/exim/mainlog on DirectAdmin. Each message line carries a two-character flag: <= arrival, => delivery, -> extra recipient in the same delivery, *> suppressed, ** bounced and == deferred. Use exigrep to pull every line for a message, exiqgrep and exiqsumm for the queue, eximstats for totals and exiwhat for live processes.
We ran these commands on our lab servers (AlmaLinux 9.8 with cPanel & WHM 11.138, and AlmaLinux 9.8 with DirectAdmin 1.712, both on Exim 4.100.1) on 7 October 2026. Sample output below is from those runs, with addresses, hostnames and IPs masked.
Table of Contents
Where the Exim logs are on cPanel and DirectAdmin
Ask Exim instead of guessing; the answer is the log_file_path setting, where %s becomes main, reject or panic:
exim -bP log_file_path
| Log | cPanel (lab: /var/log/exim_%slog) | DirectAdmin (lab: /var/log/exim/%slog) |
|---|---|---|
| Main log: arrivals, deliveries, defers, bounces | /var/log/exim_mainlog | /var/log/exim/mainlog |
| Reject log: refused connections and recipients, with headers | /var/log/exim_rejectlog | /var/log/exim/rejectlog |
| Panic log: configuration and serious errors | /var/log/exim_paniclog | /var/log/exim/paniclog |
| Rotated copies seen on the lab | exim_mainlog-20261007.gz (gzipped) | mainlog-20261005 (not gzipped) |
| Per-message log while a message is queued | /var/spool/exim/msglog/ | /var/spool/exim/msglog/ |
A non-empty panic log always deserves a look: ls -l /var/log/exim_paniclog on cPanel.
Exim log flags: <= => -> *> ** ==
Every line about a specific message starts with the date, time and message ID, followed by one of these flags (from the Exim specification):
| Flag | Meaning | What to look at next |
|---|---|---|
<= | Message arrived (accepted) | Sender after the flag, H= host, A= authenticated user, U= local user |
=> | Normal delivery | R= router, T= transport, H= remote host, C= remote reply |
-> | Another recipient delivered in the same delivery | Same fields as => |
>> | Cutthrough delivery | Rare on panel servers |
*> | Delivery suppressed by -N (testing) | Someone ran Exim with -N |
** | Delivery failed, address bounced | Error text at the end of the line |
== | Delivery deferred, temporary problem | Error text; Exim retries later |
(= | Message fakereject | ACL used fakereject |
A message is finished when its ID logs Completed. Lines like Frozen or Message is frozen mean it is stuck in the queue; see our Exim queue stuck guide.
The fields you will use most:
| Field | Meaning |
|---|---|
H= | Host name and [IP]. A name in parentheses is what the client said in HELO, not verified |
A= | Authenticator and login, for example A=dovecot_login:bob@example.com on cPanel |
U= | Local user that submitted the message (scripts, cron, PHP mail()) |
P= | On <= lines: protocol (local, esmtpa, esmtpsa…) |
S= | Size in bytes |
R= / T= | Router and transport on delivery lines. On <= lines R= is the message a bounce refers to and T= is the subject |
C= | Reply from the receiving server on delivery |
F= | Sender address on delivery and reject lines |
id= | Message-ID header of the incoming message |
exigrep: every log line for a message, user or host
exigrep searches main logs for a pattern and prints all lines for every matching message, grouped together, not just the line that matched. It reads gzipped rotated logs directly. Matching is case-insensitive; -l treats the pattern as a literal string.
exigrep bob@example.com /var/log/exim_mainlog
exigrep -l "203.0.113.10" /var/log/exim_mainlog
exigrep 1xE0Yw-00000000XWj-24Y8 /var/log/exim_mainlog-20261007.gz
exigrep -M bob@example.com /var/log/exim_mainlog # also show related bounces
Real output from the cPanel lab for one message ID (addresses masked). It shows the cwd= lines that cPanel logs because of +arguments, the arrival, the bounce and the bounce message that it caused:
2026-10-06 03:30:14 cwd=/var/spool/exim 3 args: /usr/sbin/exim -Mc 1xE0Yw-00000000XWj-24Y8
2026-10-06 03:30:14 1xE0Yw-00000000XWj-24Y8 <= root@lab.example U=root P=local S=803 id=MASKED@lab.example T="[lab.example] [Information] Backup job \"lab-accounts-to-lab2\" completed successfully" for root@lab.example
2026-10-06 03:30:14 1xE0Yw-00000000XWj-24Y8 ** root@lab.example R=localuser_root : root cannot accept local mail deliveries
2026-10-06 03:30:14 1xE0Yw-00000000XWj-24Y8 Completed
+++ 1xE0Yw-00000000XWp-31pf has not completed +++
2026-10-06 03:30:14 1xE0Yw-00000000XWp-31pf <= <> R=1xE0Yw-00000000XWj-24Y8 U=mailnull P=local S=2175 T="Mail delivery failed: returning message to sender" for root@lab.example
2026-10-06 03:30:14 1xE0Yw-00000000XWp-31pf Frozen (delivery error message)
Note the +++ ... has not completed +++ header: exigrep marks messages that have no Completed line in the logs it searched, usually because they are still queued (this bounce was frozen). The bounce has sender <> and R= pointing back at the original message.
exiqgrep and exiqsumm: search and summarise the queue
exiqgrep filters the queue listing. Selection options: -f sender regex, -r recipient regex, -o older than N seconds, -y younger than N seconds, -z frozen only, -x not frozen. Output options: -c count, -i IDs only, -b one line per message.
exim -bpc # total messages in the queue
exiqgrep -c -o 86400 # older than one day
exiqgrep -c -z # frozen
exiqgrep -f '^<>$' -i # bounces (empty sender), IDs only
exiqgrep -r '@example\.com$' -b # mail waiting for one domain
exiqgrep -f bob@example.com -i | wc -l
Real output from the DirectAdmin lab, which had a backlog of undeliverable mail to root, for exiqgrep -c -o 86400, exiqgrep -c -z and exiqgrep -c -x in that order:
78 matches out of 171 messages
0 matches out of 171 messages
171 matches out of 171 messages
exiqsumm reads exim -bp and prints one line per destination domain: count, volume, oldest, newest. -c sorts by count, -a by age:
exim -bp | exiqsumm -c
Count Volume Oldest Newest Domain
----- ------ ------ ------ ------
166 153KB 28h 1m server.example.net
3 3379 11h 2h example.com
2 2048 28h 24h server2.example.net
---------------------------------------------------------------
171 158KB 28h 1m TOTAL
One domain with a big count and an old “Oldest” is the first thing to investigate. For a scheduled version of this check, see our Exim Mail Queue Report script.
eximstats and exiwhat
eximstats builds a report from one or more main logs: totals, top senders and destinations, rejection reasons and errors. It accepts gzipped logs. Useful switches from its built-in documentation: -h0 no hourly histograms, -ne no error list, -nr no relay list, -nt no transport table, -t20 top 20 instead of top 50, -byemail group by sender address, -html=/root/eximstats.html write HTML.
eximstats -h0 -ne -nr -nt /var/log/exim_mainlog-20261007.gz | head -40
eximstats -byemail -t20 /var/log/exim_mainlog
Part of the real report from the cPanel lab:
Top 50 mail rejection reasons by message count
----------------------------------------------
Messages Mail rejection reason
22 Dropped: too many syntax or protocol errors
14 Dropped: too many unrecognized commands
10 Rejected RCPT: root cannot accept local mail deliveries
exiwhat asks every running Exim process what it is doing (it sends them SIGUSR1 and reads exim-process.info in the spool directory). Run it as root when the queue seems stuck or the load is high. The Exim manual warns it is not efficient, so do not put it in a tight loop. On the cPanel lab only the daemon was running:
17170 daemon(4.100.1): [0+0] -q15m, listening for SMTP on port 25 (IPv6 and IPv4) port 587 (IPv6 and IPv4) and for SMTPS on port 465 (IPv6 and IPv4)
Find who sent a message
Look at the <= line. The field that is present tells you how the message got in:
A=present: an authenticated SMTP login. On cPanel the authenticators aredovecot_loginanddovecot_plain; on our DirectAdmin lab they areloginandplain. The value after the colon is the mailbox that logged in. A stolen mailbox password shows up here.- No
A=,P=localand aU=user: submitted on the server itself, by a script, cron job or PHPmail(). On cPanel, the precedingcwd=line shows the directory the script ran from, which usually points at the site. - No
A=andH=is a remote host: inbound mail from another server, or relaying if your config allows it.
Top authenticated senders and top script directories in today’s log (cPanel paths; on DirectAdmin use /var/log/exim/mainlog and A=login/A=plain):
grep " <= " /var/log/exim_mainlog | grep -oE "A=dovecot_(login|plain):[^ ]+" | sort | uniq -c | sort -rn | head
grep "cwd=/home" /var/log/exim_mainlog | awk '{print $3}' | sort | uniq -c | sort -rn | head
grep " <= " /var/log/exim_mainlog | grep -oE " U=[^ ]+" | sort | uniq -c | sort -rn | head
On the lab, the cwd= count was dominated by cwd=/var/spool/exim (Exim itself) and root’s own directories, because the WordPress sites sent no mail. On a server sending spam, a single cwd=/home/user/public_html/... directory with hundreds of hits is the usual giveaway. The full procedure is in Outgoing Spam cPanel: Find the Source and Stop It.
Trace one message ID end to end
- Find the ID: from a bounce, a user complaint (“sent at 14:20 to bob@example.com”) or a search:
exigrep bob@example.com /var/log/exim_mainlog | grep " <= ". The ID is the third field, for example1xE0Yw-00000000XWj-24Y8. - Pull its full history, including rotated logs:
exigrep 1xE0Yw-00000000XWj-24Y8 /var/log/exim_mainlog*. - If it is still queued, read its per-message log and headers:
exim -Mvl 1xE0Yw-00000000XWj-24Y8andexim -Mvh 1xE0Yw-00000000XWj-24Y8. The message log shows each delivery attempt; it is written to/var/spool/exim/msglog/whenmessage_logsis on, as it was on both labs. - Check whether it was rejected rather than accepted:
grep bob@example.com /var/log/exim_rejectlog. - Read the outcome:
=>withC="250 ..."means the next server accepted it, and your part is done.==means it is waiting for a retry;**means it bounced, and the text after the address says why.
If the remote server’s reply on a ** or == line is unclear, paste it into the SMTP Bounce Explainer.
Quick reference
| Task | Command |
|---|---|
| Log path | exim -bP log_file_path |
| Count the queue | exim -bpc |
| Queue by domain | exim -bp | exiqsumm -c |
| Frozen message IDs | exiqgrep -z -i |
| Everything about an address | exigrep bob@example.com /var/log/exim_mainlog |
| One message, all logs | exigrep MSGID /var/log/exim_mainlog* |
| Headers / message log | exim -Mvh MSGID / exim -Mvl MSGID |
| Today’s bounces | grep " \*\* " /var/log/exim_mainlog | tail |
| Today’s defers | grep " == " /var/log/exim_mainlog | tail |
| Daily report | eximstats -h0 /var/log/exim_mainlog |
| What Exim is doing now | exiwhat |
Official documentation: Exim spec: log files and log line flags · Exim spec: utilities (exigrep, exiqgrep, exiqsumm, eximstats, exiwhat) · Exim spec: command line (-bp, -Mvl, -Mvh)
Related: cPanel Exim Queue Stuck: Flush, Thaw and Delete Frozen Mail · Outgoing Spam cPanel: Find the Source and Stop It · Exim Mail Queue Report · Exim Outbound Mail Limits WHM: Stop Spam Runs Fast · AI Log Analyzer for Server Logs (Apache, Nginx, Exim, MariaDB)
See also: Exim “Retry Time Not Reached for Any Host”: Causes and Fix · cPanel Exim Queue Stuck: Flush, Thaw and Delete Frozen Mail · Outgoing Spam cPanel: Find the Source and Stop It
Frequently asked questions
Where is the Exim log on cPanel?
/var/log/exim_mainlog, with exim_rejectlog and exim_paniclog next to it. Older days are rotated to dated, gzipped files such as exim_mainlog-20261007.gz.
Where is the Exim log on DirectAdmin?
/var/log/exim/mainlog, plus rejectlog and paniclog in the same folder. Run exim -bP log_file_path to confirm on your server.
What does == mean in the Exim log?
The delivery was deferred because of a temporary problem. Exim keeps the message and retries according to its retry rules. The text at the end of the line gives the reason.
What is the difference between exigrep and grep?
grep prints only matching lines. exigrep finds messages with a matching line and prints every log line for those messages, grouped, so you see arrival, delivery attempts and completion together.
How do I see who sent a message through Exim?
Look at the arrival line with the <= flag. A= shows the authenticated mailbox, U= the local user for scripts, and H= the sending host. On cPanel the cwd= line before it shows the script directory.
Can exigrep read rotated .gz logs?
Yes. exigrep passes compressed files through zcat automatically, so exigrep pattern /var/log/exim_mainlog* searches current and rotated logs in one go.