Skip to content

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

curl -sI https://domain.tld/apps/visitors/login.php | grep -i content-type   # must be text/html

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:

memory_limit=512M
max_execution_time=300
max_input_vars=10000

3. Quota Values & Units

Dovecot reports STORAGE in KB.

  • 61757097 KB = 60,309 MB ≈ 58.9 GiB
  • 94371840 KB = 92,160 MB = 90 GiB
  • cPanel account limit 256000 is in MB = 250 GB
/scripts/generate_maildirsize --confirm --allaccounts USER
quota -vs -u USER

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.

findmnt /tmp /var/tmp
/scripts/securetmp   # /tmp and /var/tmp marked noauto will not mount at boot

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

doveadm who -1 | awk '$3=="pop3"'

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.