cPanel / Dovecot Troubleshooting — Command Reference¶
Session compilation. Assume root. cPanel/WHM + Apache or LiteSpeed.
1. PHP Files Downloading Instead of Executing¶
Find stale handler directives in .htaccess¶
grep -rniE 'AddHandler|AddType|application/x-httpd|SetHandler|php_value|php_flag' \
/home/*/public_html/apps/visitors/.htaccess \
/home/*/public_html/support/.htaccess \
/home/*/public_html/.htaccess
Remove/comment legacy lines such as:
AddHandler application/x-httpd-php5 .php
AddType application/x-httpd-php53 .php
AddHandler x-httpd-php5-cgi .php
Check and set the PHP version binding¶
/usr/local/cpanel/bin/whmapi1 php_get_vhost_versions | grep -A3 domain.tld
/usr/local/cpanel/bin/whmapi1 php_set_vhost_versions vhost-0=domain.tld version=ea-php82
vHost / permissions / rebuild¶
grep -rn "php" /etc/apache2/conf.d/userdata/std/2_4/USER/domain.tld/*.conf 2>/dev/null
stat -c '%a %U:%G %n' /home/USER/public_html/apps/visitors/login.php # want 644 user:user
/scripts/rebuildhttpdconf && systemctl restart httpd # or: systemctl restart lsws
Verify¶
2. HTTP 500 Errors¶
Read the logs first¶
tail -100 /home/USER/logs/domain.tld.error_log
tail -100 /usr/local/apache/logs/error_log # or /usr/local/lsws/logs/stderr.log
tail -50 /home/USER/public_html/forms/error_log
Temporarily surface the fatal¶
cd /home/USER/public_html/forms
echo -e "display_errors=On\nlog_errors=On\nerror_log=/home/USER/forms_php_errors.log" > .user.ini
# remove .user.ini after diagnosis
Common causes¶
# missing extensions
php -m | grep -Ei 'zip|mbstring|iconv|intl|gd'
yum install ea-php82-php-zip ea-php82-php-mbstring ea-php82-php-intl -y
# permissions
find /home/USER/public_html/forms -type d \( ! -perm 755 -a ! -perm 750 \) -ls
chown -R USER:USER /home/USER/public_html/forms
# CloudLinux LVE limits
lveinfo --period=1d --user=USER
Limit bumps for .user.ini / MultiPHP INI Editor:
3. Quota Values & Units¶
Dovecot reports STORAGE in KB.
61757097 KB= 60,309 MB ≈ 58.9 GiB94371840 KB= 92,160 MB = 90 GiB- cPanel account limit
256000is in MB = 250 GB
4. Maildir vs mdbox Detection¶
| Format | Markers |
|---|---|
| Maildir | cur/, new/, tmp/, maildirsize, dovecot-uidlist, .Folder dot-dirs |
| mdbox | storage/ (m.1, m.2, dovecot.map.index*), mailboxes/<Folder>/dbox-Mails, dovecot-quota |
ls -d /home/cpuser/mail/domain.tld/user/{cur,new,tmp,maildirsize} 2>/dev/null # Maildir
ls -d /home/cpuser/mail/domain.tld/user/{storage,mailboxes} 2>/dev/null # mdbox
cat /home/cpuser/mail/domain.tld/user/mailbox_format.cpanel
grep -i mail_location /var/cpanel/dovecot/mailbox_format.conf 2>/dev/null
/usr/local/cpanel/scripts/email_storage_format --show --user=cpuser
doveadm user -f mail user@domain.tld
Classify every account on the server¶
for d in /home/*/mail/*/*; do [ -d "$d" ] || continue; \
if [ -d "$d/storage" ]; then echo "mdbox $d"; \
elif [ -d "$d/cur" ]; then echo "maildir $d"; fi; done
Clean stale Maildir leftovers after mdbox conversion¶
# verify no real Maildir data remains first
find /home/cpuser/mail/domain.tld/user -type d -name cur \
-exec sh -c 'echo "$1: $(ls -1 "$1" | wc -l)"' _ {} \;
cd /home/cpuser/mail/domain.tld/user
rm -f maildirsize maildirfolder dovecot-uidlist
doveadm quota recalc -u user@domain.tld
doveadm quota get -u user@domain.tld
5. Filesystem Attributes & Type¶
e in lsattr = extent mapping, normal on ext4, absent on XFS. Harmless.
Problem flags are i (immutable) and a (append-only).
lsattr /home/user/mail/domain/user/ | grep -E '^-{4}[ia]|[ia]-{2}'
chattr -ia /home/user/mail/domain/user/maildirsize # only if i/a is set
stat -f -c '%T' always prints ext2/ext3 — ext2/3/4 share magic 0xEF53. Use instead:
findmnt -no SOURCE,FSTYPE --target /home
df -T /home/USER
blkid /dev/mapper/almalinux-root
tune2fs -l $(findmnt -no SOURCE --target /home) | grep -E 'Filesystem features|Default mount'
6. Permission denied in SysQuota.pm + Broken Quotas¶
Standard cPanel mail permissions¶
| Path | Mode | Owner |
|---|---|---|
/home/user |
0711 |
user:user |
/home/user/mail |
0751 |
user:user |
mail/domain.tld |
0751 |
user:user |
mail/domain.tld/account |
0751 |
user:user |
cur/ new/ tmp/ |
0700 |
user:user |
maildirsize |
0600 |
user:user |
/home/user/etc/domain.tld |
0750 |
user:mail |
Locate the failing path component¶
namei -om /home/USER/mail/domain.tld/account/maildirsize
getfacl -p /home/USER /home/USER/mail /home/USER/mail/domain.tld 2>/dev/null | grep -v '^#'
lsattr -d /home/USER /home/USER/mail /home/USER/mail/domain.tld
find /home/USER/mail ! -user USER -o ! -group USER | head -50
# classic post-migration cause: UID/GID mismatch
id USER; stat -c '%u:%g' /home/USER
Fix permissions (use cPanel's tool, not manual chmod)¶
chown -R USER:USER /home/USER/mail
/usr/local/cpanel/bin/mailperm --verbose
sudo -u USER stat /home/USER/mail/domain.tld/account/maildirsize
Fix quotaon.service failure¶
ls -l /quota.user /aquota.user 2>/dev/null
repquota -a | head
quotaoff -a
quotacheck -avugm -F vfsv1 # slow on large /home
quotaon -avug
/scripts/fixquotas
/scripts/generate_maildirsize --confirm --allaccounts USER
quota -vs -u USER
Order matters: fix mail permissions first, or fixquotas hits the same EPERM.
Related fstab check¶
7. Stuck doveadm expunge (cPanel Email Disk Usage)¶
Symptom: doveadm expunge ... sentbefore 365days at 99% CPU for days, folder locked.
kill PID1 PID2
sleep 5; kill -9 PID1 PID2 2>/dev/null
doveadm -v force-resync -u user@domain.tld -f INBOX '*'
doveadm index -u user@domain.tld '*'
doveadm quota recalc -u user@domain.tld
doveadm purge -u user@domain.tld # mdbox, if force-resync reports broken refs
time doveadm -v expunge -u user@domain.tld mailbox Trash sentbefore 365days
Watchdog — kill expunges running over 2h¶
ps -eo pid,etimes,cmd | awk '/doveadm.*expunge/ && $2>7200 {print $1}' | xargs -r kill -9
ps aux | grep -c '[d]oveadm.*expunge'
8. POP3 / IMAP Traffic Analysis¶
Reading the Dovecot disconnect line¶
pop3(user@dom)<pid><sess>: Disconnected: Logged out top=0/0, retr=0/0, del=0/1124, size=1299889711, bytes=24/41618
| Field | Meaning | Sum it? |
|---|---|---|
bytes=in/out |
actual network traffic | YES |
retr=count/bytes |
messages downloaded | YES |
top=count/bytes |
headers fetched | YES |
del=x/y |
deleted / total messages | no |
size= |
mailbox size on disk, re-reported each session | NEVER |
retr=0/0 with large bytes=out means the client is only running UIDL/LIST
(~37 bytes per message) and downloading nothing. Traffic scales as
message count × poll frequency.
Distinct accounts that authenticated¶
grep -oP 'dovecot: (imap|pop3)-login: .*user=<\K[^>]+' /var/log/maillog | sort -u | wc -l
cat /home/*/etc/*/passwd 2>/dev/null | wc -l # accounts that exist
wc -l /home/USER/etc/*/passwd | tail -1
Top POP3 accounts by login count¶
zcat -f /var/log/maillog* | grep -oP 'pop3-login: Login: user=<\K[^>]+(?=>)' \
| sort | uniq -c | sort -rn | head -20
# single domain
zcat -f /var/log/maillog* | grep -oP 'pop3-login: Login: user=<\K[^>]+@example\.com(?=>)' \
| sort | uniq -c | sort -rn
Bytes + polls + overhead per account (single domain)¶
zcat -f /var/log/maillog* | awk '
/dovecot: pop3\(.*@example\.com\)/ {
match($0,/pop3\(([^)]+)\)/,u)
match($0,/bytes=([0-9]+)\/([0-9]+)/,b)
match($0,/size=([0-9]+)/,s)
match($0,/retr=[0-9]+\/([0-9]+)/,r)
match($0,/top=[0-9]+\/([0-9]+)/,tp)
in_[u[1]]+=b[1]; out[u[1]]+=b[2]; data[u[1]]+=r[1]+tp[1]
if (s[1]+0 > sz[u[1]]) sz[u[1]]=s[1]
n[u[1]]++
}
END {
printf "%12s %12s %12s %8s %10s %s\n","OUT_GiB","MAILDATA_GiB","OVERHEAD_GiB","polls","mbox_MiB","account"
for (x in out) {
printf "%12.3f %12.3f %12.3f %8d %10.1f %s\n",
out[x]/1073741824, data[x]/1073741824, (out[x]-data[x])/1073741824, n[x], sz[x]/1048576, x
T+=out[x]; D+=data[x]
}
printf "\nTOTAL out: %.3f GiB | actual mail: %.3f GiB | protocol overhead: %.3f GiB\n",
T/1073741824, D/1073741824, (T-D)/1073741824
}' | sort -k1 -rn
Server-wide bytes per POP3 account¶
zcat -f /var/log/maillog* | awk '
/dovecot: pop3\(/ {
match($0,/pop3\(([^)]+)\)/,u); match($0,/bytes=[0-9]+\/([0-9]+)/,b)
t[u[1]]+=b[1]; n[u[1]]++
} END {for (x in t) printf "%8.2f MB %6d polls %s\n", t[x]/1048576, n[x], x}' \
| sort -rn | head -20
Source IPs and poll interval¶
zcat -f /var/log/maillog* | grep 'pop3-login: Login' \
| grep -oP 'user=<\K[^>]+|rip=\K[^,]+' | paste - - | sort | uniq -c | sort -rn | head -20
# median seconds between polls for one account
zcat -f /var/log/maillog* | grep 'pop3-login: Login.*user@example.com' \
| awk '{print $1,$2,$3}' | while read m d t; do date -d "$m $d $t" +%s; done \
| sort -n | awk 'NR>1{print $1-p} {p=$1}' | sort -n \
| awk '{a[NR]=$1} END{print "median interval:",a[int(NR/2)],"s"}'
Confirm a re-download / polling loop¶
zcat -f /var/log/maillog* | grep "pop3(user@example.com" | grep -oP 'del=\K[0-9]+' | sort | uniq -c
zcat -f /var/log/maillog* | grep -c "pop3-login.*user=<user@example.com>"
# manual sanity check: bytes_per_poll * polls
echo "scale=2; 41618*28642/1073741824" | bc
Cross-check against cPanel bandwidth accounting¶
sqlite3 /var/cpanel/bandwidth/USER.sqlite \
"select protocol, sum(bytes)/1073741824.0 from bandwidth_data group by protocol;"
Bandwidth JSON keys are Unix timestamps at each year's start; values are bytes.
Divide by 1073741824 for GiB.
Live sessions¶
9. Remediation for POP3 Abuse¶
# shrink the mailbox -> shrinks every future UIDL listing
doveadm expunge -u user@example.com mailbox INBOX savedbefore 180d
doveadm quota recalc -u user@example.com
# rebuild unstable UIDLs (mdbox)
doveadm force-resync -u user@example.com '*'
# throttle at the firewall (Dovecot POP3 has no rate limit)
echo 'CONNLIMIT = "110;30,995;30"' >> /etc/csf/csf.conf && csf -r
csf -a 1.2.3.4
Client-side: raise poll interval to 15–30 min, disable "leave a copy on server", or migrate the account to IMAP.
Extend log retention — /etc/logrotate.d/syslog defaults to 4 weeks; bandwidth
questions span years.
10. Third-Party Dovecot Traffic Monitoring¶
| Tool | Use |
|---|---|
| Netdata | Auto-detects Dovecot via doveadm stats dump; lowest-effort dashboards |
| vnstat | Per-interface historical totals |
| mtail / grok_exporter | maillog → Prometheus; only option for long-term per-account attribution |
| lnav | SQL queries over maillog/rotated logs; great for ad-hoc bytes= aggregation |
| GoAccess | Real-time TUI/HTML with a custom maillog format |
| Zabbix / Munin | Fleet templates using doveadm stats |
| ntopng / iftop / nethogs | Per-IP flows; catch an abuser live |
bash <(curl -Ss https://my-netdata.io/kickstart.sh) --disable-telemetry
yum install vnstat -y && systemctl enable --now vnstatd
vnstat -i eth0 -h
Dovecot 2.3+ native metrics endpoint:
# /etc/dovecot/conf.d/90-stats.conf
service stats { inet_listener http { port = 9900 } }
metric pop3_bytes { filter = event=mail_user_session_finished }
Dovecot's own stats reset on restart and are not persisted — keep parsing maillog for long-term attribution.