Cron Job Troubleshooting Guide
Cron jobs fail in spectacular and quiet ways. The spectacular failures — a missing binary, a syntax error in the crontab, a Kubernetes pod that won't schedule — are easy to find because something obviously went wrong. The quiet failures are worse: the job "runs", the exit code is 0, but the actual work didn't happen. This guide walks through common categories of cron problems, with diagnostic steps that usually surface the root cause.
My Cron Job Doesn't Run At All
Start by confirming that cron itself is even attempting to fire your schedule. On many Linux distributions, cron records job invocations in syslog or the service journal:
# Debian / Ubuntu grep CRON /var/log/syslog | tail -50 # RHEL / Amazon Linux / CentOS grep CROND /var/log/cron | tail -50 # systemd-based distros journalctl -u cron --since "1 hour ago"
If you see lines like (USER) CMD (/path/to/script.sh) at the expected times, cron is firing your job and the problem lies inside the script. If those lines are missing, cron is not seeing the schedule at all. The most common reasons:
- The cron daemon is stopped. Check with
systemctl status cron(Debian) orsystemctl status crond(RHEL). On a fresh container image, the daemon often isn't enabled by default. - The crontab has a syntax error.
crontab -lwill show what was installed. Check the service journal or distribution-specific cron log for parse errors; exact logging varies by implementation. - The schedule is in the wrong field. A typo like
0 * 9 * 1-5instead of0 9 * * 1-5moves the "9" into the day-of-month slot, which means the job runs at minute 0 of every hour, but only on the 9th of the month. Validate every new schedule against CronWizard's human-readable translation before deploying. - You edited the wrong crontab.
crontab -eas root edits root's crontab; as a regular user, it edits that user's./etc/crontaband/etc/cron.d/*require an extra user field. Confirmwhoamimatches the crontab you intended to edit.
It Works When I Run It Manually but Not From Cron
This is by far the most common failure mode in practice. Cron does not run your job in your interactive shell. It runs with a smaller environment and generally does not source your shell profile. Variables, aliases, and paths available in a terminal may therefore be absent or different.
The fix is to make the script self-sufficient:
#!/bin/bash set -euo pipefail # Pin PATH explicitly. Cron's default is often just /usr/bin:/bin export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Source the env file your application uses set -a source /opt/myapp/.env set +a # Use absolute paths for every binary /usr/bin/python3 /opt/myapp/jobs/daily_report.py
A useful debugging trick is to dump the cron environment to a file once and inspect it:
* * * * * env > /tmp/cron-env.txt 2>&1
Wait a minute, then cat /tmp/cron-env.txt. The difference between that file and env in your interactive shell is the gap your script has to fill in.
It Runs at the Wrong Time
Start with timezone and clock configuration, but also verify field order, day-of-week numbering, and the target scheduler's day-field semantics. Traditional cron usually evaluates schedules in the daemon or host timezone; supported overrides vary by implementation:
- Cronie supports a
CRON_TZdirective before schedule entries, for exampleCRON_TZ=UTC. Other cron implementations may not support it. - The host's system timezone (
/etc/timezoneon Debian, the/etc/localtimesymlink on most distros). - For Kubernetes CronJobs (1.27+), the
.spec.timeZonefield. Without it, the kube-controller-manager's local timezone is used; do not assume which zone the cluster operator configured.
Diagnose by running date inside the same context as the cron job:
* * * * * date >> /tmp/cron-date.log
If date reports a different timezone than you expected, that is the source of the wrong-time bug. Once you know what cron thinks the time is, adjusting the schedule (or pinning a timezone) is mechanical.
It Runs but the Output Is Lost
By default, cron emails the output of every job to the user's local mailbox. Some systems do not configure local mail delivery, which makes that output difficult to find. To capture output explicitly, redirect both stdout and stderr to a managed log file:
0 2 * * * /opt/bin/backup.sh >> /var/log/myapp/backup.log 2>&1
The 2>&1 sends stderr to the same destination as stdout. Without it, errors follow the cron implementation's normal mail or logging behavior. Pair the log file with logrotate so it does not grow without bound.
On Kubernetes, container logs are captured automatically by the kubelet; just make sure your job writes to stdout/stderr instead of an in-container log file that gets discarded when the pod terminates.
It Runs Twice (or More)
Duplicate runs usually fall into one of these categories:
- The same schedule installed twice. Check
crontab -lfor every user, plus/etc/crontab,/etc/cron.d/, and/etc/cron.{hourly,daily,weekly,monthly}/. Old deployments left behind in/etc/cron.d/are a classic source of mystery duplicates. - Multiple replicas without locking. If you run cron inside a Kubernetes Deployment with replicas > 1, every replica fires the schedule independently. The right tool for this is a Kubernetes CronJob (which schedules one Job per fire) plus
concurrencyPolicy: Forbid. - A long-running job overlapping itself. A 5-minute schedule that takes 7 minutes will overlap. See the best practices guide for locking patterns that prevent this.
Kubernetes CronJob-Specific Failures
Kubernetes CronJobs add their own layer of failure modes. The diagnostic flow is:
kubectl get cronjob <name>— confirmLAST SCHEDULEmatches what you expect.kubectl describe cronjob <name>— look atEvents. Most scheduling failures (quota, missing service account, invalid pod spec) appear here.kubectl get jobs --selector=cronjob-name=<name>— list the Job objects the CronJob created. Each one corresponds to one schedule fire.kubectl logs job/<job-name>— read the actual container logs.
A common silent failure is hitting the successfulJobsHistoryLimit or failedJobsHistoryLimit defaults (3 and 1 respectively). After three successful runs, the oldest Job is garbage-collected, taking its logs with it. For jobs you actually need to debug after the fact, raise these limits or ship logs to a long-term store.
For a deeper dive into Kubernetes CronJob behavior, including theconcurrencyPolicy, startingDeadlineSeconds, and timezone handling, see the dedicated Kubernetes CronJob guide.
Frequently Asked Questions
Why is my cron job not running at all?
Common causes include a stopped daemon, a rejected crontab, permissions, a minimal runtime environment, or an unexpected timezone. Inspect the cron service journal or distribution-specific cron log first, while remembering that logging detail depends on the cron implementation and system configuration.
Why does my cron job work when I run it manually but not when cron runs it?
Cron runs your script with a minimal environment — no PATH, no shell aliases, no profile sourcing. Use absolute paths to every binary (/usr/bin/python3, not python3), set PATH explicitly at the top of your script or crontab, and source any required environment files (e.g. source /etc/environment) before doing anything else.
Why is my cron job running at the wrong time?
First compare the expression, scheduler timezone, current clock, and scheduler-specific day-field rules. Traditional cron normally evaluates against the daemon or host timezone; some implementations support CRON_TZ. On Kubernetes 1.27+, set spec.timeZone on the CronJob instead of assuming the controller timezone.
Why did my Kubernetes CronJob stop catching up on missed schedules?
Kubernetes does not automatically set spec.suspend for this condition. If the controller counts more than 100 missed schedules within its scheduling window, it does not create a catch-up Job and records an error. Check spec.suspend, startingDeadlineSeconds, status.lastScheduleTime, controller logs, events, clock skew, and resource quotas.
Why did my cron job run twice within seconds?
A few possibilities: (1) you have the same crontab installed for two different users, (2) the schedule is duplicated across /etc/crontab and /etc/cron.d/, (3) on Kubernetes, concurrencyPolicy is Allow and a previous run was still in progress, or (4) you ran a manual invocation that overlapped with the scheduled one. Searching the host configuration for the script name can reveal duplicates.