Total control of cpanel/whm updates: auditing cpupdate.conf, yum.conf, and protecting critical packages
Back to blog

Total control of cpanel/whm updates: auditing cpupdate.conf, yum.conf, and protecting critical packages

6/7/2026 · 3 min · Infrastructure

Operational problem: automatic updates as an instability vector#

In cPanel/WHM production environments, the server's default behavior is to update itself automatically. The upcp cron job runs daily, and the OS package manager (yum/dnf) can be triggered by any script with root privileges. The practical consequences in critical environments:

The objective of this audit: completely lock the server's update autonomy, transferring exclusive control to the administrator, no exceptions.

Layer 1 - auditing upcp: the cPanel update daemon#

upcp (cPanel Update CPanel) is the script that cPanel installs at /usr/local/cpanel/scripts/upcp and registers in the root crontab:

# View the upcp cron installed by cPanel
crontab -l | grep upcp
# 0 0 * * * /usr/local/cpanel/scripts/upcp --cron >> /var/log/cpupdate.log 2>&1

# View the last upcp execution logs
tail -50 /var/log/cpupdate.log

upcp execution flow:

/etc/cpupdate.conf
    │
    ▼
  upcp --cron (root, 00:00 daily)
    │
    ├── Checks CPANEL= → if not 'manual', updates the panel
    ├── Checks RPMUP= → if not 'manual', updates OS RPMs
    └── Checks UPDATES= → general policy

Layer 2 - configuring /etc/cpupdate.conf#

This is the central update control file for cPanel. Checking current state before any intervention:

cat /etc/cpupdate.conf

Default state (auto-update behavior active):

CPANEL=release
RPMUP=daily
SARULESUP=daily
STAGING_DIR=/usr/local/cpanel
UPDATES=daily

Meaning of each directive:

Directivedaily/release valuemanual value
CPANELAutomatically updates cPanel/WHM to the latest version of the release tierFreezes the current cPanel version - only updates when upcp is manually executed
RPMUPAllows cPanel to update system RPM packages (Apache, PHP, etc.)Prevents any automatic RPM management by cPanel
UPDATESGeneral policy - defines default behaviorFull manual mode - no automatic updates of any category
SARULESUPAutomatically updates SpamAssassin rulesKept at daily - antispam rules are signature updates, not code changes

Final applied configuration:

# Edit the configuration file
nano /etc/cpupdate.conf
CPANEL=manual
RPMUP=manual
SARULESUP=daily
STAGING_DIR=/usr/local/cpanel
UPDATES=manual

Verifying the configuration was absorbed:

# Test upcp in dry-run mode without applying changes
/usr/local/cpanel/scripts/upcp --force --noupdate 2>&1 | head -20

# Check current cPanel version (should remain the same after the next cron cycle)
cat /usr/local/cpanel/version
# 116.0.x.x (or the version installed at the time of the audit)

Verifying the upcp cron still exists but is controlled by the conf:

# The cron REMAINS active - it runs, reads the conf, and does nothing because UPDATES=manual
crontab -l | grep upcp
# 0 0 * * * /usr/local/cpanel/scripts/upcp --cron >> /var/log/cpupdate.log 2>&1

Important: Do not remove the upcp cron. The script does more than just update - it also runs integrity checks and generates logs that cPanel support uses for diagnostics. The manual directive in cpupdate.conf is the correct control mechanism.

Layer 3 - blocking critical packages in yum/dnf#

Even with cPanel in manual mode, the OS's native package manager remains functional and can update critical packages if another script, another administrator, or an installed dependency runs yum update or dnf upgrade without filters.

Identifying the correct file for the OS:

# AlmaLinux 8/9, CentOS 8, CloudLinux 8/9, RHEL 8/9
ls /etc/dnf/dnf.conf
# If it exists, use this one

# CentOS 7, CloudLinux 7
ls /etc/yum.conf
# If it exists, use this one

# Verify which package manager is active
which dnf && echo "dnf" || which yum && echo "yum"

Checking current state before modification:

# AlmaLinux/CloudLinux 8/9
grep "exclude" /etc/dnf/dnf.conf
# → No output = no exclusions configured

# CentOS 7
grep "exclude" /etc/yum.conf
# → No output = no exclusions configured

Adding the exclude directive to the [main] section:

For AlmaLinux/CloudLinux 8/9 (/etc/dnf/dnf.conf):

[main]
gpgcheck=1
installonly_limit=3
clean_requirements_on_remove=True
best=True
skip_if_unavailable=False
exclude=kernel* httpd* mysql* mariadb* php*

For CentOS 7 (/etc/yum.conf):

[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=5
bugtracker_url=...
distroverpkg=centos-release
exclude=kernel* httpd* mysql* mariadb* php*

Anatomy of the exclude directive:

exclude=kernel* httpd* mysql* mariadb* php*
    │       │       │       │        │
    │       │       │       │        └── php, php-fpm, php-common, php-cli, ...
    │       │       │       └─────────── mariadb, mariadb-libs, mariadb-server, ...
    │       │       └─────────────────── mysql, mysql-community-server, ...
    │       └─────────────────────────── httpd, httpd-tools, httpd-devel, ...
    └─────────────────────────────────── kernel, kernel-devel, kernel-headers, ...

The wildcard captures all sub-packages and variants. Example: httpd blocks both httpd and httpd-tools, httpd-devel, and mod_ssl.

Testing that exclusions are active:

# List what would be updated (without applying)
dnf check-update 2>/dev/null | grep -E "kernel|httpd|mysql|mariadb|php"
# → No output = excluded packages do not appear as available for update

# Confirm the exclusion is configured
dnf config-manager --dump | grep exclude
# → exclude = kernel* httpd* mysql* mariadb* php*

Testing forced manual installation (for maintenance window):

During a planned maintenance window, to specifically apply the update to an excluded package:

# Force update of an excluded package (override of the exclusion rule)
dnf update httpd --disableexcludes=main

# Or update only the kernel with full control
dnf update kernel --disableexcludes=main

Layer 4 - configuration integrity verification#

After applying both layers, verify the consolidated state:

echo "=== cPanel Update Config ==="
cat /etc/cpupdate.conf

echo ""
echo "=== Yum/DNF Exclude Rules ==="
grep -E "exclude|gpgcheck" /etc/dnf/dnf.conf 2>/dev/null || \
grep -E "exclude|gpgcheck" /etc/yum.conf 2>/dev/null

echo ""
echo "=== cPanel Current Version ==="
cat /usr/local/cpanel/version

echo ""
echo "=== Kernel Current Version ==="
uname -r

echo ""
echo "=== Apache Current Version ==="
httpd -v 2>/dev/null || apachectl -v 2>/dev/null

Expected output after the audit:

=== cPanel Update Config ===
CPANEL=manual
RPMUP=manual
SARULESUP=daily
STAGING_DIR=/usr/local/cpanel
UPDATES=manual

=== Yum/DNF Exclude Rules ===
exclude=kernel* httpd* mysql* mariadb* php*
gpgcheck=1

=== cPanel Current Version ===
116.0.12.3

=== Kernel Current Version ===
5.14.0-362.8.1.el9_3.x86_64

=== Apache Current Version ===
Server version: Apache/2.4.58 (AlmaLinux)

Unlock procedure for maintenance Windows#

To ensure updates are controlled (not permanently blocked), the application procedure during a maintenance window is:

# 1. Notify users / activate maintenance mode in the application

# 2. Perform full server backup via WHM
#    WHM → Backup → Generate Backup

# 3. Manually update cPanel
/usr/local/cpanel/scripts/upcp --force

# 4. Update operating system packages (with exclusions temporarily suspended)
dnf update --disableexcludes=main

# 5. Restart services if necessary
systemctl restart httpd
systemctl restart mysql

# 6. Reactivate exclusions (already active - --disableexcludes is only for the command)
# No action required - exclusions remain in dnf.conf

# 7. Verify services returned healthy
systemctl status httpd mysql php-fpm

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments