The Security Blind Spot
Cron jobs often run as root, handle database credentials, and process sensitive data. Yet they rarely receive the same security scrutiny as web applications or APIs. This makes them attractive targets for attackers and a common source of privilege escalation.
1. Never Run as Root (Unless Necessary)
Most cron jobs do not need root privileges. Running as root means a vulnerability in your script gives an attacker full system access.
# Bad: root crontab
0 2 * * * /path/to/backup.sh
# Better: dedicated service user
# In /etc/cron.d/backup
0 2 * * * backup-user /path/to/backup.sh
Create a dedicated user with minimal permissions:
useradd -r -s /usr/sbin/nologin backup-user
chown backup-user:backup-user /path/to/backup.sh
chmod 750 /path/to/backup.sh
2. Secure Credential Storage
Never hardcode credentials in cron scripts or crontab entries. They end up in version control, process listings, and log files.
Bad: Credentials in Script
#!/bin/bash
# NEVER DO THIS
pg_dump "postgresql://admin:s3cret@localhost/mydb" > backup.sql
Better: Environment File
# /etc/cron.d/backup
0 2 * * * backup-user /bin/bash -c 'source /etc/backup/.env && /path/to/backup.sh'
# /etc/backup/.env (chmod 600, owned by backup-user)
export DATABASE_URL="postgresql://admin:s3cret@localhost/mydb"
Best: Secrets Manager
#!/bin/bash
# Fetch credentials at runtime from secrets manager
DATABASE_URL=$(aws secretsmanager get-secret-value \
--secret-id prod/database-url \
--query 'SecretString' --output text)
pg_dump "$DATABASE_URL" > backup.sql
3. Restrict File Permissions
# Script files: owner read+execute only
chmod 750 /path/to/cron-scripts/*.sh
# Output/log files: owner read+write only
chmod 640 /var/log/cron-jobs/*.log
# Credential files: owner read only
chmod 600 /etc/backup/.env
# Backup files: owner read only
chmod 600 /backups/*.sql.gz
4. Prevent Command Injection
If your cron job processes filenames, user input, or external data, unquoted variables can lead to command injection.
# VULNERABLE: unquoted variable allows injection
FILE=$(curl -s https://api.example.com/latest-file)
cat $FILE # If FILE contains "; rm -rf /", game over
# SAFE: quote all variables
FILE=$(curl -s https://api.example.com/latest-file)
cat "$FILE"
# SAFER: validate input before using it
FILE=$(curl -s https://api.example.com/latest-file)
if [[ ! "$FILE" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "Invalid filename" >&2
exit 1
fi
cat "$FILE"
5. Use Cron Access Control
Limit which users can create cron jobs:
# /etc/cron.allow - only these users can use crontab
root
deploy
backup-user
# /etc/cron.deny - deny specific users (if cron.allow doesn't exist)
nobody
www-data
6. Audit Cron Changes
Monitor changes to crontab files. An attacker who gains access to a server often installs persistence via cron jobs.
# Watch for crontab changes with auditd
auditctl -w /etc/crontab -p wa -k cron_change
auditctl -w /etc/cron.d/ -p wa -k cron_change
auditctl -w /var/spool/cron/ -p wa -k cron_change
7. Network Security for Check-ins
When your cron jobs check in with external monitoring, those HTTP requests can be intercepted or spoofed.
- Always use HTTPS for monitoring check-ins
- Use unique, unguessable monitor IDs so attackers cannot fake check-ins
- Consider IP allowlisting on your monitoring service
8. Limit Network Access
If a cron job only needs to access localhost and a monitoring service, restrict its outbound network access with firewall rules:
# iptables: allow backup-user only to database and monitoring
iptables -A OUTPUT -m owner --uid-owner backup-user -d 127.0.0.1 -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner backup-user -d cronguard.app -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner backup-user -j DROP
9. Log Everything
Comprehensive logging provides an audit trail for security investigations:
#!/bin/bash
set -euo pipefail
LOG="/var/log/cron-jobs/backup.log"
exec >> "$LOG" 2>&1
echo "[$(date -Iseconds)] Job started by $(whoami) from $(hostname)"
echo "[$(date -Iseconds)] Working directory: $(pwd)"
# ... actual work ...
echo "[$(date -Iseconds)] Job completed successfully"
Security Checklist
- Run with least-privilege user (not root)
- Store credentials in secrets manager or encrypted env files
- Set strict file permissions (750 scripts, 600 secrets)
- Quote all variables to prevent injection
- Validate external input before processing
- Use /etc/cron.allow to restrict cron access
- Audit crontab changes with auditd
- Use HTTPS for all external communications
- Log all actions with timestamps
- Restrict network access per user
Conclusion
Cron jobs are part of your attack surface. They run automatically, often with elevated privileges, and handle sensitive data. Applying basic security practices (least privilege, credential management, input validation, and auditing) dramatically reduces the risk of a cron job becoming an attacker's persistence mechanism or data exfiltration tool.