Find and Delete Hidden Backdoors & Malicious Cron Jobs on Linux
Spot rogue crons, SSH key implants, and reverse shells on your Linux server—then remove them safely with copy-paste commands, verification checks, and clear guidance on when Fixwebnode should take over.
If your Linux VPS or small-business server feels “owned”—odd CPU spikes, mystery outbound connections, files that reappear after you delete them—you need a methodical hunt for hidden backdoors and malicious cron jobs, not guesswork.
This guide walks homeowners, agencies, and SMB operators through practical checks on common distributions (Ubuntu, Debian, Alma/RHEL-family). You will inventory cron and systemd timers, audit SSH persistence, catch reverse shells and web shells, and verify the box is clean. When the trail goes deeper than DIY, Fixwebnode server support can finish the cleanup and harden the host so the same implant does not return.
Why hidden backdoors and malicious crons matter on Linux servers
Attackers rarely leave a single obvious binary. They chain persistence: a cron line that curls a payload every five minutes, an extra key in authorized_keys, a systemd user timer, or a PHP web shell under a theme directory. From a small shop site to a home lab exposed on the public internet, the symptoms look the same—reboots that “fix” nothing, mail queues full of spam, or ss showing connections you never opened.
DIY is appropriate when you still have root, logs are intact, and you can take a short maintenance window. It is not appropriate when ransomware is active, customers’ data may already be exfiltrated, or every cleanup step is undone within minutes. Fixwebnode works across Australian service areas and remote Linux estates; see all service areas if you need on-call coverage beyond a single city.
Common issues you will actually see
These problems are distinct. Treat each as its own investigation track so you do not miss a second persistence path while fixing the first.
- Issue 1 — Malicious or obfuscated cron jobs that reinstall malware. Symptoms: high load at fixed intervals, unknown scripts under
/tmp,/var/tmp, or/dev/shm, and jobs that reappear after you delete a binary. Often base64-wrapped one-liners in root or www-data crontabs. - Issue 2 — Hidden SSH backdoors via authorized_keys and unexpected users. Symptoms: successful logins from unfamiliar IPs in
auth.log/secure, extra keys with no comment matching your team, or new local users with UID 0 or sudo rights. - Issue 3 — Reverse shells, rogue processes, and systemd timer implants. Symptoms: long-lived
bash -i,nc,python -c, orcurl|shprocesses; odd listening ports; user-level systemd units that survive reboot without a classic cron entry. - Issue 4 — Web shells and droppers in document roots. Symptoms: POST spikes to single PHP/ASP files, files with recent mtimes in upload or cache folders, and outbound HTTP from the web user even when the site is idle.
Issue 1 — Find and remove malicious cron jobs
Cron is still the most common “set and forget” persistence on compromised Linux hosts. Check every user and every system drop-in directory—not only crontab -l as root.
Step 1 — List every crontab and system cron directory
sudo crontab -l
sudo ls -la /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly
sudo grep -RsnE '.+' /etc/cron.d/ /etc/crontab 2>/dev/null
for u in $(cut -f1 -d: /etc/passwd); do echo "===== $u ====="; sudo crontab -u "$u" -l 2>/dev/null; done
Flag lines that download remote content (curl, wget, fetch), pipe to shells, write under /tmp, or call filenames that look random.
Step 2 — Inspect at and systemd timers (cron alternatives)
sudo atq
systemctl list-timers --all
systemctl list-unit-files --type=timer
ls -la /etc/systemd/system /etc/systemd/system/*.timer /var/spool/cron 2>/dev/null
Step 3 — Quarantine, do not only delete blindly
Copy suspect scripts aside, then remove the job so you keep forensic evidence:
sudo mkdir -p /root/incident-quarantine/$(date +%F)
sudo cp -a /path/to/suspect-script /root/incident-quarantine/$(date +%F)/
sudo crontab -e
# or edit the matching file under /etc/cron.d/
sudo systemctl disable --now suspicious.timer 2>/dev/null
sudo rm -f /etc/systemd/system/suspicious.timer /etc/systemd/system/suspicious.service
sudo systemctl daemon-reload
Step 4 — Verify the schedule is gone
sudo grep -RsnE 'curl|wget|base64|/tmp/|/dev/shm' /etc/cron* /var/spool/cron 2>/dev/null
systemctl list-timers --all
ps auxf | head -n 50
Watch CPU and network for one full cron cycle (often 5–15 minutes). If the payload returns, another persistence path still exists—continue with Issues 2–4 rather than looping on the same file.
When to call Fixwebnode: jobs rewrite themselves after removal, root’s crontab is empty but load still spikes on a schedule, or you cannot tell legitimate backup crons from hostile ones on a busy host.
Issue 2 — Hunt SSH key backdoors and rogue accounts
Password lockouts do nothing if an attacker already dropped a public key. Audit keys and users before you rotate anything else.
Step 1 — Inventory authorized_keys for every home and system account
sudo find /root /home /etc -name authorized_keys -type f 2>/dev/null
sudo sh -c 'for f in $(find /root /home -name authorized_keys 2>/dev/null); do echo "FILE $f"; cat "$f"; echo; done'
Remove keys you do not recognize. Prefer deleting the whole line over commenting if the key is confirmed hostile.
Step 2 — Find unexpected users, UID 0 clones, and sudoers drift
awk -F: '($3 == 0) { print }' /etc/passwd
getent passwd | awk -F: '$3 >= 1000 { print }'
sudo grep -RsnE '.' /etc/sudoers /etc/sudoers.d 2>/dev/null
last -a | head -n 40
sudo grep -E 'Accepted|Invalid user|session opened' /var/log/auth.log 2>/dev/null | tail -n 80
sudo grep -E 'Accepted|Invalid user' /var/log/secure 2>/dev/null | tail -n 80
Step 3 — Lock down SSH configuration
sudo cp -a /etc/ssh/sshd_config /root/incident-quarantine/sshd_config.bak
sudo grep -E '^(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|AuthorizedKeysFile|AllowUsers|Port)' /etc/ssh/sshd_config
# After edits you trust:
sudo sshd -t && sudo systemctl reload sshd || sudo systemctl reload ssh
Disable password auth only after your own key works in a second session. Kill unknown sessions:
who
sudo ss -tnp | grep ':22'
# identify PID then: sudo kill PID
Step 4 — Verify
sudo find /root /home -name authorized_keys -exec wc -l {} \;
sudo tail -n 50 /var/log/auth.log 2>/dev/null
sudo tail -n 50 /var/log/secure 2>/dev/null
When to call a pro: you see multiple UID 0 accounts, host keys were replaced, or bastion/jump-host trust means one bad key unlocks a fleet. Pair deep SSH cleanup with broader hardening via Secure Linux Admin & Full-Stack Infrastructure Australia when the server is part of a multi-host stack.
Issue 3 — Catch reverse shells, rogue processes, and timer implants
If cron looks clean but the box still phones home, inspect live process trees, sockets, and user systemd units.
Step 1 — Process and socket triage
ps auxf
ps -eo pid,ppid,user,cmd --sort=start_time | tail -n 40
sudo ss -tulnp
sudo ss -tnp state established
sudo lsof -i -n -P 2>/dev/null | head -n 100
Suspicious patterns include interpreters with -c payloads, nc -e, /dev/tcp/ bash redirects, mining binaries, and listeners on high ports bound to 0.0.0.0 that you never configured.
Step 2 — Map executable path and kill cleanly
sudo ls -l /proc/PID/exe /proc/PID/cwd
sudo tr '\0' ' ' < /proc/PID/cmdline; echo
sudo kill PID
# if it ignores SIGTERM:
sudo kill -9 PID
Quarantine the binary path shown by /proc/PID/exe before deletion if the file still exists on disk.
Step 3 — User and system systemd persistence
systemctl list-units --type=service --all | grep -viE 'loaded|active \(running\) .* (ssh|cron|nginx|apache|mysql|postgres)'
ls -la /etc/systemd/system /usr/lib/systemd/system
sudo find /home /root -path '*/.config/systemd/user/*' 2>/dev/null
sudo find /var/spool/cron /var/spool/at -type f 2>/dev/null
Disable and remove unknown units, then daemon-reload. Reboot into a maintenance window if processes respawn faster than you can kill them.
Step 4 — Verify no callback remains
sudo ss -tnp state established
ps aux | grep -E 'nc|ncat|bash -i|python|perl|ruby|curl|wget' | grep -v grep
sudo journalctl -xe --since "1 hour ago" | tail -n 100
When to call Fixwebnode: the parent process is a legitimate service (web, DB) that keeps re-spawning the shell, kernel modules look tampered, or you need packet capture and IOC extraction without taking production down longer than necessary.
Issue 4 — Locate and remove web shells and droppers
Shared hosting-style layouts and CMS installs are frequent drop zones. Work from the document root outward.
Step 1 — Find recently changed and odd web files
sudo find /var/www /srv /home/*/public_html -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.php*' \) -mtime -14 -ls 2>/dev/null
sudo find /var/www -type f -name '*.php' -size -50k -exec grep -lE 'eval\s*\(|base64_decode\s*\(|gzinflate|shell_exec|passthru|assert\s*\(' {} \; 2>/dev/null
Step 2 — Quarantine matches and fix ownership
sudo mkdir -p /root/incident-quarantine/web
sudo mv /var/www/html/path/to/shell.php /root/incident-quarantine/web/
sudo chown -R root:root /root/incident-quarantine
sudo find /var/www -type f -name '*.php' -perm -0002 -ls
Restore clean copies from known-good backups or vendor packages. Rotate CMS and database credentials after removal.
Step 3 — Confirm the web user is quiet
sudo ss -tnp | grep -E 'nginx|apache|httpd|php-fpm'
sudo tail -n 100 /var/log/nginx/access.log 2>/dev/null
sudo tail -n 100 /var/log/apache2/access.log 2>/dev/null
sudo tail -n 100 /var/log/httpd/access_log 2>/dev/null
When to call a pro: the site is a multi-vhost production box, malware reinfects on every deploy, or package metadata is broken while you try to reinstall clean packages—for example corrupted APT configuration that blocks security updates. In that case use specialist help such as Fix Corrupted Linux APT Sources.list - Alabama Expert Support alongside malware removal so patching works again.
When DIY is enough vs when to book Fixwebnode
DIY is enough when you retain exclusive root access, you can snapshot or back up first, only one persistence mechanism shows up, and after cleanup a full timer cycle plus reboot shows no rogue processes, no unknown keys, and no hostile cron lines. Document every file you moved into /root/incident-quarantine.
Book Fixwebnode when any of the following is true: persistence returns after a clean reboot; you suspect lateral movement to other servers; customer PII may have left the host; disk rootkits or unexpected kernel modules appear; you cannot safely rotate secrets alone; or you need a hardened baseline (SSH, firewall, unattended upgrades, monitoring) applied consistently after the incident. Australian small businesses and remote operators can start from the same runbook above, then hand off for verification and lock-down rather than living on a half-cleaned host.
Practical handoff checklist to speed support: note distro and version (cat /etc/os-release), whether the host is bare metal or VPS, time the odd behavior started, outputs of crontab/ss/authorized_keys searches, and whether a snapshot exists.
Close the loop and get help if you need it
Hidden backdoors on Linux almost always combine schedule (cron/timers), access (SSH keys/users), and execution (shells or web droppers). Work those three layers in order, quarantine instead of silent delete, and verify with process, socket, and log checks after every change.
If you want a specialist to finish the hunt, validate the clean state, and harden the server so the next scan is boring, start a conversation through Fixwebnode server support. Bring the quarantine folder and command outputs from this guide—you will spend less time on triage and more time back on a trustworthy host.