The notification reads "Maintenance ended; however, it did not exit cleanly (256). The following events were logged: 'scripts/update-packages'." It names the stage that failed and nothing else. Meanwhile the server keeps running the old build, the failure repeats every night, and the mail gets filtered into a folder nobody opens.
Work through these in order. The first two cover most of what actually happens on a production fleet.
Read the log before you touch anything
Every run writes /var/cpanel/updatelogs/update.<unix timestamp>.log, and last symlinks to
the most recent one:
tail -60 /var/cpanel/updatelogs/last
grep -Ei 'fatal|blocker|failed|did not exit cleanly' /var/cpanel/updatelogs/last | tail -20
/usr/local/cpanel/cpanel -V
The version output tells you whether you are behind at all — compare it against the tier you are subscribed to in cPanel's update tiers. A server on RELEASE that is two majors back has been failing for months, not days.
Stage: scripts/update-packages
This is the OS package stage, not cPanel's own code. upcp shelled out to dnf (or yum on
EL7), the transaction failed, and cPanel reported the non-zero exit. Reproduce it directly —
the real error is in the package manager's output, not cPanel's:
dnf clean all
dnf -y update
Typical causes, in the order they show up: a third-party repo (Remi, IUS, an old ELevate leftover) offering a conflicting version, a GPG key that expired, or a mirror returning 403 because the OS release is EOL. Remove or pin the offending repo, then repair cPanel's own package set and re-run:
/usr/local/cpanel/scripts/check_cpanel_pkgs --fix
/usr/local/cpanel/scripts/upcp --cron
Stage: the update never started
If the log ends within seconds and mentions a blocked version change, a blocker fired. The ones that come up in practice:
| Blocker | Fix |
|---|---|
| Invalid or expired license | Diagnose the license error, then renew your cPanel license |
| OS past cPanel's support window | Move CentOS 7 to AlmaLinux |
| MySQL/MariaDB below the new minimum | Upgrade the database in WHM |
Invalid mailserver or ftpserver value in /var/cpanel/cpanel.config | Set them to a valid value (dovecot; pure-ftpd or proftpd) |
| Account transfer in progress | Finish or abort it in WHM → Transfers → Review Transfers and Restores |
Blockers cannot be overridden, and --force does not bypass them. An in-flight transfer is
the one that catches people out — a stalled restore from the
Transfer Tool holds updates off indefinitely
until someone clears it.
Disk space, inodes, and read-only mounts
A major upgrade needs a few GB free; 3 GB on /usr is a sane floor, and /var needs room for
the staged files.
df -h /usr /var /tmp
df -i /usr /var
mount | grep -E ' (ro|read-only)'
du -sh /usr/local/cpanel/logs/archive /usr/local/apache/logs/archive
Archived logs and core dumps are the usual offenders. Inode exhaustion produces the same failure with plenty of gigabytes still showing — check both.
Stuck runs and stale locks
If upcp reports that maintenance is already running but nothing is progressing, confirm
before you intervene:
ps auxf | grep -E 'upcp|updatenow' | grep -v grep
stat -c '%y' /var/cpanel/updatelogs/last
A log whose mtime has not moved in 30 minutes with no live process is a stale run. Clear it by re-running in a terminal multiplexer so an SSH drop cannot orphan it again:
tmux new -s upcp
/usr/local/cpanel/scripts/upcp --force
--force reinstalls the current version and upgrades regardless of preferences. --sync
repairs the installed version without pulling a newer one — that is the safer first attempt
when you suspect a partial install rather than a missed upgrade.
After an OS major upgrade
If the server was ELevated or otherwise moved between EL major versions, check that cPanel knows what it is running on:
grep rpm_dist_ver /var/cpanel/sysinfo.config
rpm_dist_ver=unknown makes every subsequent update fail at the package stage. Regenerate the
file and re-run the update:
/usr/local/cpanel/scripts/gensysinfo
/usr/local/cpanel/scripts/upcp --cron