
Running a cPanel/WHM server means handling customer email issues on a daily basis. One of the most common (and frustrating) problems? A customer suddenly reports that all of their emails are missing. Was it deleted by a user? Did a POP3 client download and remove them? Or was there unauthorized access?
In this guide, we’ll walk you through:
- How to troubleshoot missing emails in cPanel/WHM
- How to check Dovecot, Exim, and cPanel logs
- A ready-to-run Linux shell script that audits email deletions and logins
- SEO-friendly best practices for system admins
Why Emails Disappear in cPanel
Before blaming the server, it’s important to understand why emails may vanish:
- POP3 Download – If a user configures their mail client (Outlook, Thunderbird, etc.) with POP3, it downloads and removes mail from the server.
- IMAP Deletion – If a user deletes mail from their phone or client, IMAP sync removes it everywhere.
- Webmail (Roundcube/Horde) – Users may delete emails via Webmail.
- Filters/Forwarders – Misconfigured filters may silently delete or forward mail.
- Compromised Account – If the password was leaked, an attacker might have accessed and deleted emails.
Step-by-Step Troubleshooting
You can check mail logs manually:
# Check if emails were expunged (deleted via IMAP)
grep 'Expunge' /var/log/maillog | grep 'user@domain.com'
# Check POP3 activity
grep 'POP3' /var/log/maillog | grep 'user@domain.com'
# Check Webmail & cPanel access logs
grep 'user@domain.com' /usr/local/cpanel/logs/access_log
But running all these commands repeatedly is painful. That’s why we built a mail audit script.
Mail Audit Script for cPanel/WHM
Here’s a complete script that scans current and rotated logs for IMAP deletions, POP3 downloads, and Webmail logins. It even summarizes the IP addresses involved.
Save as
/root/mail_audit.sh
, make executable, then run with the target email (and optionally the cPanel username if you know it).bash /root/mail_audit.sh -e user@domain.com -u CPANELUSER
#!/usr/bin/env bash
# mail_audit.sh — Audit who deleted/downloaded emails for a cPanel mailbox
# Works on WHM/cPanel servers using Dovecot/Exim. Scans current + rotated logs.
# Usage:
# ./mail_audit.sh -e user@domain.com [-u CPANEL_USERNAME]
#
# Examples:
# ./mail_audit.sh -e info@example.com
# ./mail_audit.sh -e sales@example.com -u example
set -euo pipefail
EMAIL=""
CPUSER=""
while getopts ":e:u:" opt; do
case $opt in
e) EMAIL="$OPTARG" ;;
u) CPUSER="$OPTARG" ;;
\?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
:) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;;
esac
done
if [[ -z "${EMAIL}" ]]; then
echo "Usage: $0 -e user@domain.com [-u CPANEL_USERNAME]"
exit 1
fi
DOMAIN="${EMAIL#*@}"
LOCALPART="${EMAIL%@*}"
ts() { date +"%Y-%m-%d %H:%M:%S"; }
log_files=(
/var/log/maillog
/var/log/maillog-*
/var/log/dovecot
/var/log/dovecot-info.log
/var/log/dovecot*
/var/log/exim_mainlog
/var/log/exim_mainlog-*
/usr/local/cpanel/logs/access_log
/usr/local/cpanel/logs/access_log-*
/var/cpanel/roundcube/log/*
)
# Also search per-account Roundcube logs if CPUSER provided
if [[ -n "${CPUSER}" ]]; then
log_files+=("/home/${CPUSER}/logs/roundcube/*" "/home/${CPUSER}/etc/${DOMAIN}/logs/roundcube/*")
fi
# De-duplicate existing files
declare -A seen
existing_files=()
for f in "${log_files[@]}"; do
for m in $f; do
if [[ -e "$m" ]] && [[ -z "${seen[$m]:-}" ]]; then
existing_files+=("$m")
seen[$m]=1
fi
done
done
if [[ ${#existing_files[@]} -eq 0 ]]; then
echo "[$(ts)] No log files found. Are you running on a WHM/cPanel server?"
exit 1
fi
# Grep wrapper that auto zgreps .gz
g() {
local pattern="$1"; shift
local file="$1"
if [[ "$file" =~ \.gz$ ]]; then
zgrep -i --no-filename --line-number "$pattern" "$file" 2>/dev/null || true
else
grep -i --no-filename --line-number "$pattern" "$file" 2>/dev/null || true
fi
}
separator() {
echo "========================================================================"
}
echo
separator
echo "[ $(ts) ] MAIL AUDIT REPORT for ${EMAIL}"
separator
echo
# 1) IMAP Expunge / Delete events (Dovecot)
echo "1) IMAP delete events (Dovecot 'expunge'/'deleted'):"
echo "-----------------------------------------------------------------------"
expunge_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
g "expunge|deleted|delete" "$f" | grep -i "$EMAIL" || true
done | tee "$expunge_tmp"
if [[ ! -s "$expunge_tmp" ]]; then
echo "No IMAP delete/expunge lines found for ${EMAIL}."
fi
echo
# 2) POP3 downloads (messages pulled from server)
echo "2) POP3 access (client may download & remove from server):"
echo "-----------------------------------------------------------------------"
pop_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
g "pop3" "$f" | grep -i "$EMAIL" || true
done | tee "$pop_tmp"
if [[ ! -s "$pop_tmp" ]]; then
echo "No POP3 activity found for ${EMAIL}."
fi
echo
# 3) IMAP logins (to correlate IPs)
echo "3) IMAP logins/sessions (Dovecot):"
echo "-----------------------------------------------------------------------"
imap_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
g "imap" "$f" | grep -i "$EMAIL" || true
done | tee "$imap_tmp"
if [[ ! -s "$imap_tmp" ]]; then
echo "No IMAP login/session lines found for ${EMAIL}."
fi
echo
# 4) Roundcube/Webmail logs
echo "4) Roundcube/Webmail logins/errors:"
echo "-----------------------------------------------------------------------"
rc_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
case "$f" in
*roundcube*|*/access_log*|*cpanel* )
g "$EMAIL|$LOCALPART@$DOMAIN|$LOCALPART%40$DOMAIN" "$f" || true
;;
esac
done | tee "$rc_tmp"
if [[ ! -s "$rc_tmp" ]]; then
echo "No Roundcube/Webmail entries found for ${EMAIL}."
fi
echo
# 5) Exim deliveries/forwards (to check if mail was delivered/forwarded elsewhere)
echo "5) Exim deliveries/forwards for this address:"
echo "-----------------------------------------------------------------------"
exim_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
case "$f" in
*exim_mainlog* )
g "$EMAIL" "$f" || true
;;
esac
done | tee "$exim_tmp"
if [[ ! -s "$exim_tmp" ]]; then
echo "No Exim entries found for ${EMAIL}."
fi
echo
# 6) cPanel/Webmail access logs (who logged in via webmail/cPanel UI)
echo "6) cPanel/Webmail access (IPs, timestamps):"
echo "-----------------------------------------------------------------------"
cp_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
case "$f" in
*/access_log* )
# Match either full email or percent-encoded form in URLs
g "$EMAIL|$LOCALPART%40$DOMAIN" "$f" || true
;;
esac
done | tee "$cp_tmp"
if [[ ! -s "$cp_tmp" ]]; then
echo "No cPanel/Webmail access lines for ${EMAIL}."
fi
echo
# 7) Summaries: IPs involved in deletes / logins
ip_re='([0-9]{1,3}\.){3}[0-9]{1,3}'
summarize_ips () {
local label="$1"; local file="$2"
echo "Top IPs for ${label}:"
echo "-----------------------------------------------------------------------"
if [[ -s "$file" ]]; then
# Try to extract IPv4s and count
grep -Eo "$ip_re" "$file" | sort | uniq -c | sort -nr | head -20
else
echo "No data."
fi
echo
}
echo "7) IP Summaries"
separator
summarize_ips "IMAP delete/expunge" "$expunge_tmp"
summarize_ips "POP3 access" "$pop_tmp"
summarize_ips "IMAP logins" "$imap_tmp"
summarize_ips "Roundcube/Webmail + cPanel access" "$rc_tmp"
summarize_ips "cPanel/Webmail access_log" "$cp_tmp"
# 8) Hints
echo "Hints:"
echo "- If you see 'expunge' with an IP: that's a delete via IMAP from that IP/client."
echo "- POP3 lines with your email indicate a client likely downloaded mail (often removing from server)."
echo "- Webmail/cPanel access lines show who logged into the UI and from which IP."
echo "- Cross-check unfamiliar IPs with the customer. If unknown, reset the mailbox password and re-scan."
echo
separator
echo "[ $(ts) ] End of report for ${EMAIL}"
separator
How to run
sudo bash /root/mail_audit.sh -e user@domain.com
# If you know the cPanel username for better Roundcube paths:
sudo bash /root/mail_audit.sh -e user@domain.com -u CPANELUSER
What This Script Reports
- IMAP Deletions (Expunge events) → Confirms if emails were deleted via IMAP, with IP address.
- POP3 Downloads → Indicates if emails were pulled and removed by a mail client.
- Webmail & cPanel Access → Shows who logged in via Webmail, from where, and when.
- IP Summaries → Lists the top IP addresses that accessed the mailbox.
Common Errors
/root/mail_audit.sh: line 10: $'\r': command not found : invalid option nameline 11: set: pipefail
Ah that error ($'\r': command not found
) happens because the script was saved with Windows (CRLF) line endings, not Linux (LF).
Linux shell (bash
) chokes on those hidden \r
carriage return characters
Fix
Option 1: Convert the file
Run:
dos2unix /root/mail_audit.sh
If dos2unix
isn’t installed:
yum install dos2unix -y # CentOS/RHEL/AlmaLinux
apt install dos2unix -y # Debian/Ubuntu
Then re-run:
bash /root/mail_audit.sh -e user@domain.com -u CPANELUSER
Option 2: Use sed
(no install needed)
If you can’t install dos2unix
:
sed -i 's/\r$//' /root/mail_audit.sh
Option 3: When Copy-Pasting
If you paste directly into nano
or vim
on the server, make sure your editor is set to Unix line endings.
Example:
nano -c /root/mail_audit.sh
# paste script, save with CTRL+O then CTRL+X
After conversion, the errors will disappear and the script will run properly.