
A compromised cPanel server rarely announces itself. You usually see symptoms instead: PHP slows down, pages start redirecting, users complain about “sent from your site” spam, or a routine plugin update triggers 500 errors.
This cPanel malware scan tutorial gives you a repeatable, account-by-account workflow. You’ll verify what’s happening, identify the affected user, quarantine and clean without unnecessary downtime, and then fix the entry point so it stays clean.
The steps apply to typical cPanel & WHM stacks on AlmaLinux/Rocky Linux with Apache (EasyApache 4) or Nginx in front, PHP-FPM, and Exim. You’ll use WHM where it’s fastest, then run a few production-safe CLI checks to narrow the blast radius.
What you’ll need before you start (and what not to do)
- Root access to the server (SSH + WHM).
- A clean backup point or at least recent backups you can restore from.
- 30–60 minutes of low-traffic time if you expect to temporarily disable plugins/themes or place an account into maintenance.
Skip “one-click cleaners” that remove anything suspicious across every account. On shared hosting, that kind of sweep can delete legitimate vendor code, break autoloaders, and create a second outage you now have to explain.
Aim for controlled quarantine, proof, and targeted cleanup.
If you don’t already have a restore runbook, stop here and make one. You should be confident you can restore before you touch production files.
HostMyCode has a practical guide: VPS backup verification tutorial.
Step 1: Triage the symptoms and confirm it’s not just caching or a bad deploy
Confirm the behavior from outside the server first. Two quick checks catch most SEO spam and conditional redirects.
-
Fetch the homepage and one deep URL with a normal User-Agent and with Googlebot.
curl -sS -A "Mozilla/5.0" https://example.com/ | head curl -sS -A "Googlebot/2.1 (+http://www.google.com/bot.html)" https://example.com/ | headIf the output differs, you’re likely dealing with conditional injection.
-
Check for suspicious outbound mail patterns if users report spam. On cPanel servers, big spikes often trace back to one account.
exim -bpc exim -bp | head -50If the queue keeps growing, plan to contain outbound mail once you identify the offender (covered later).
Also rule out an SSL problem that’s being reported as “hacked.” Browser warnings and failed Let’s Encrypt renewals often look like compromise to users.
If you need help, use: SSL renewal troubleshooting tutorial.
Step 2: Run a controlled cPanel malware scan tutorial workflow (WHM + CLI)
cPanel servers can have several security tools in play, depending on your licensing and provider. Common examples include cPHulk (brute-force protection), optional Imunify360, optional ClamAV, plus any host-specific scanners.
Your goal is repeatability. Use the same sequence every time, and keep notes you can hand to someone else.
2.1 Start with WHM: check for obvious account compromise indicators
-
In WHM, review Security Center → Recent Root Logins and Last Login IP Address for anything you don’t recognize.
-
Open Server Status → Daily Process Log. Look for repeated PHP processes under a single user, or strange binaries launched from
/home/*. -
Check Account Information → List Accounts for unexpected shell access or odd contact emails.
2.2 Identify the infected account via log pivoting
For redirects and injected content, web server logs are usually the fastest path to the right account. Use whatever matches your stack:
-
Apache (common):
# Top vhosts by requests (last 10k lines) tail -n 10000 /etc/apache2/logs/access_log | awk '{print $1}' | sort | uniq -c | sort -nr | head # If you know the domain, find its vhost log ls -1 /usr/local/apache/domlogs/ | grep -i example.com # Search for suspicious query strings, base64, eval, or long parameters grep -RIn --binary-files=without-match "base64\|eval(\|gzinflate\|shell_exec\|passthru\|assert(\|preg_replace\s*(.*\/e" /usr/local/apache/domlogs/example.com | head -
Nginx in front (if configured): check the Nginx access logs for the domain or upstream location.
Once you have a likely domain, map it to the cPanel user:
whmapi1 getdomainowner domain=example.com
Write down the username and scope the next steps to that account first. Don’t widen the scan until you have a reason.
2.3 Scan the account files with safe, targeted searches
You can often spot the problem with a few high-signal searches before you run heavier scanners:
# Replace USER with the cPanel username
USER="exampleuser"
HOMEDIR="/home/$USER"
# 1) New or recently modified PHP files (last 7 days)
find "$HOMEDIR/public_html" -type f \( -name "*.php" -o -name "*.phtml" \) -mtime -7 -print | head -200
# 2) High-signal strings (not perfect, but fast)
grep -RIn --binary-files=without-match \
"base64_decode\|gzinflate\|str_rot13\|eval(\|system(\|shell_exec\|passthru\|popen\|proc_open\|assert(\|curl_exec\|file_get_contents\s*(\s*'http" \
"$HOMEDIR/public_html" | head -200
# 3) Hidden dotfiles and odd writable directories
find "$HOMEDIR/public_html" -maxdepth 3 -type f -name ".*.php" -o -type d -name ".*" | head -200
Two patterns show up constantly on shared hosting:
- Malware tucked into
wp-includes,wp-admin, or a must-use plugin folder (wp-content/mu-plugins). - A single “loader” in
index.phporheader.phpthat fetches a remote payload.
2.4 If you have ClamAV installed, scan only the suspect home first
Full-disk scans on busy servers can turn into an I/O problem. Start with the user’s home directory and keep the output:
clamscan -r --infected --bell --max-filesize=50M --max-scansize=200M \
"/home/exampleuser" | tee /root/clamav-exampleuser-$(date +%F).log
If you don’t have ClamAV, you can still do solid triage with the targeted searches above.
If you do add ClamAV, schedule scans off-peak and watch iowait on dense servers.
Already seeing high iowait? Address that before you run anything heavy. Use HostMyCode’s flow: Server disk I/O troubleshooting tutorial.
Step 3: Contain the damage (without wiping the evidence)
Containment gives you breathing room. It also prevents the attacker (or an automated reinfection) from dropping the same payload again while you clean.
3.1 Quarantine suspicious files instead of deleting immediately
Create a quarantine directory outside the public web root. Move only the files you’re confident are malicious.
Preserve timestamps and permissions so you can review the incident later.
USER="exampleuser"
QUAR="/home/$USER/quarantine-$(date +%F)"
mkdir -p "$QUAR"
chown "$USER:$USER" "$QUAR"
chmod 700 "$QUAR"
# Example: move a suspected webshell
mv /home/$USER/public_html/wp-content/uploads/2026/07/cache.php "$QUAR/"
If you’re not sure, copy first and leave the original in place until you confirm:
cp -a /home/$USER/public_html/suspect.php "$QUAR/"
3.2 Temporarily lock down write access in common abuse paths
Reinfection often happens because PHP can write into plugin and theme directories. WordPress usually needs write access in wp-content/uploads (and sometimes specific cache directories).
It usually does not need write access in core code paths.
For a short containment window, tighten permissions:
# Remove world-writable permissions under public_html
chmod -R o-w /home/exampleuser/public_html
# Ensure uploads remains writable for the user (adjust as needed)
chmod -R u+rwX /home/exampleuser/public_html/wp-content/uploads
Don’t “chmod 777” your way through permission errors. That approach reliably makes a bad situation worse.
3.3 Contain outbound email if the account is spamming
If the account is sending spam, reduce damage quickly. You can suspend the account in WHM (fast) or rate-limit it based on your policy.
If you need a targeted mail fix after DNS or server changes, see: cPanel email routing tutorial.
Step 4: Clean WordPress (or other CMS) the way you can defend
Cleaning isn’t “delete random PHP until the site loads.” You want a state you can defend.
That means pristine core files, plugins and themes you can account for, and no mystery admins left behind.
4.1 For WordPress: replace core, don’t trust it
From the account, pull a fresh copy of WordPress and replace core directories. Run this as the correct user so ownership stays correct.
su - exampleuser
cd ~/public_html
# Put the site into maintenance (optional but reduces churn)
php -r 'file_put_contents(".maintenance", "<?php $upgrading = time(); ?>");'
# Download WordPress core
curl -sSLo /tmp/wordpress.tar.gz https://wordpress.org/latest.tar.gz
mkdir -p /tmp/wp-clean
tar -xzf /tmp/wordpress.tar.gz -C /tmp/wp-clean
# Replace core directories and files (keep wp-config.php and wp-content)
rsync -a --delete /tmp/wp-clean/wordpress/wp-admin/ ./wp-admin/
rsync -a --delete /tmp/wp-clean/wordpress/wp-includes/ ./wp-includes/
rsync -a /tmp/wp-clean/wordpress/*.php ./
# Remove maintenance flag
rm -f .maintenance
Why this works: attackers love hiding in small core diffs. Replacing core gives you a known baseline in minutes.
4.2 Plugins and themes: reinstall from known sources
Where possible, reinstall plugins and themes from trusted publishers. For custom themes, compare against your repository or a clean backup.
If you can’t prove the source, treat it as suspect until you can.
Quick checks that often surface injected code:
# Look for unexpected PHP in uploads
find wp-content/uploads -type f -name "*.php" -print | head -200
# Look for recently modified plugin/theme files
find wp-content/plugins wp-content/themes -type f \( -name "*.php" -o -name "*.js" \) -mtime -14 -print | head -200
4.3 Reset WordPress admin access and remove rogue users
It’s common to find hidden admins added during the compromise. If WP-CLI is available:
# List admins
wp user list --role=administrator --path=/home/exampleuser/public_html
# Remove a rogue admin (example)
wp user delete badadmin --reassign=1 --path=/home/exampleuser/public_html
Then rotate credentials that matter:
- WordPress admin passwords
- Database user password (and update
wp-config.php) - cPanel account password
Step 5: Verify the server-side entry point (the part many cleanups miss)
Cleaning site files alone often leads to a repeat incident. If an FTP user is compromised, a cPanel password is leaked, or a vulnerable plugin remains, you’ll be right back where you started.
5.1 Audit access paths: FTP, SFTP, SSH, and API tokens
- Disable unused FTP accounts; prefer SFTP with keys where possible.
- Check
~/.ssh/authorized_keysfor unexpected keys (for users with shell access). - Review cPanel API tokens if used by external systems.
If you’re moving clients from FTP to a locked-down SFTP model, follow: SFTP setup guide tutorial.
5.2 Check for cross-account risk on the server
On multi-tenant servers, a single weak account can become a foothold for lateral movement. Confirm your isolation features (CageFS if available, PHP-FPM per-user, correct file ownership).
Also make sure you don’t have writable web roots owned by nobody.
A quick ownership sanity check on the infected account:
USER="exampleuser"
find /home/$USER/public_html -maxdepth 4 -type f \( -name "*.php" -o -name "*.js" \) -user nobody -print | head -200
5.3 Add basic HTTP-level blocking for common exploit paths
After you clean up, bots will keep hammering /wp-login.php and XML-RPC endpoints. If you run Nginx in front, rate limiting reduces brute-force noise and blocks a lot of low-effort exploit traffic.
See: Nginx rate limiting tutorial.
Step 6: Post-clean validation checklist (don’t skip this)
Validation is how you stop guessing and start trusting the result. Run this after you think the account is clean.
- Re-scan: run your grep scan again and confirm the same indicators are gone.
- External fetch: compare normal User-Agent vs Googlebot output again.
- Search for hidden PHP in uploads: expect zero results.
- Mail queue: confirm it’s stable (not steadily increasing).
- File integrity: for WordPress core, confirm files match the official distribution (replaced in Step 4.1).
- Browser test: open devtools, watch for injected JS calls to unfamiliar domains.
If the infection returns within hours, stop repeating the same cleanup. That pattern usually means a credential leak or a still-vulnerable plugin/theme.
Fix the entry point before you touch files again.
Step 7: Prevention that actually reduces repeat incidents on cPanel
Hardening doesn’t need to be extreme. Set consistent defaults, keep software current, and make sure your recovery process is real.
7.1 Patch cadence: OS, cPanel, PHP, and CMS
- Keep cPanel updated to a supported release tier that fits your change window.
- Remove old PHP versions you don’t need. Each extra version expands attack surface.
- For WordPress: auto-update minor core releases and security updates; be strict on plugin provenance.
7.2 Safer admin access: don’t expose panels to the whole internet
At minimum, restrict WHM/cPanel access by IP where practical. Also avoid logging in from shared Wi‑Fi.
If you manage multiple servers, use a jump host so fewer admin surfaces sit open to the public internet. HostMyCode’s guide is practical: SSH jump host setup guide tutorial.
7.3 Enforce 2FA for WHM, cPanel, and resellers
Credential stuffing still works because too many hosting logins are password-only. Enforce 2FA wherever you can, and treat reseller accounts as high-risk.
Use: cPanel 2FA setup tutorial.
7.4 Backups: optimize for restores, not just retention
A clean restore often beats a fragile manual cleanup. Keep multiple restore points, test them, and document the order of operations (DNS, SSL, app secrets, mail).
For a restore-first approach, follow: disaster recovery tutorial.
Common pitfalls that cause “cleaned but reinfected” loops
- Leaving a vulnerable plugin enabled because the site “needs it.” Replace it or isolate the site.
- Ignoring additional domains/addon domains under the same cPanel user. Clean them all.
- Not rotating credentials (WordPress admin, cPanel, database, FTP/SFTP).
- Keeping writable code directories (plugins/themes) for convenience.
- Cleaning files but not mail abuse, leading to blacklists and long-term deliverability damage.
Summary: a repeatable cleanup flow you can run under pressure
This cPanel malware scan tutorial reduces the incident to a sequence you can run the same way every time. Verify symptoms, identify the account from logs, scan narrowly, and quarantine instead of deleting.
Then replace CMS core, reinstall known-good plugins/themes, rotate credentials, and harden access and backups. It’s not clever. It’s dependable, and that’s what you need during an incident.
If you’re doing this on an overloaded server—or one with constant churn—consider moving to a VPS where you control the stack and visibility. A HostMyCode VPS gives you dedicated resources for scanning and cleanup, while managed VPS hosting fits better if you want help with patching, hardening, and recovery processes.
If you’re tired of repeat malware incidents, start with stronger isolation and a recovery plan you can actually execute. HostMyCode can help you move off shared environments to a server you control, with migration support and security defaults that reduce repeat compromises.
Compare a HostMyCode VPS for hands-on administration, or choose managed VPS hosting if you want a guided hardening and maintenance approach.
FAQ
Should I scan the entire server or just one cPanel account?
Start with the suspected account for faster results and lower load. Once you confirm a real infection, expand to other accounts that share the same plugins/themes or show unusual log patterns.
Is it safe to delete infected files immediately?
Quarantine first. Moving or copying suspected files to a non-web directory keeps evidence and gives you an easy rollback if you flag a legitimate file.
Why does malware come back after I clean the WordPress files?
The entry point is still open: a vulnerable plugin, stolen credentials, or writable code paths. Rotate passwords, reinstall plugins from trusted sources, and tighten write permissions so PHP can’t modify code directories.
What’s the fastest way to identify which cPanel user is infected?
Pivot from web logs. Find the domain showing redirects or suspicious requests, then map it to the owner with whmapi1 getdomainowner domain=.... Scan that user’s public_html first.
Will suspending an account stop spam immediately?
It usually stops web-based mail scripts running under that account, but queued messages can remain. Check the Exim queue and only remove or bounce messages if you understand the impact on legitimate mail.