惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
H
Help Net Security
Last Week in AI
Last Week in AI
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
B
Blog
C
Check Point Blog
T
Tailwind CSS Blog
云风的 BLOG
云风的 BLOG
D
Docker
Recent Announcements
Recent Announcements
Vercel News
Vercel News
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
月光博客
月光博客
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
The Register - Security
The Register - Security
V
Visual Studio Blog
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
PCI Perspectives
PCI Perspectives
Cisco Talos Blog
Cisco Talos Blog
博客园 - Franky
D
DataBreaches.Net
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
P
Palo Alto Networks Blog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
T
Tenable Blog
L
LINUX DO - 热门话题
Spread Privacy
Spread Privacy

DigitalOcean Community Tutorials

It's Time to Break Up with Your Cloud: Why AI Teams are Switching We Built a Private-Document AI App to Test Platform Security. Here Is What We Could Actually Verify. PostgreSQL Explained: A Complete Beginner-to-Advanced Guide How To Install and Configure Postfix on Ubuntu How To Build a Web Application Using Flask in Python 3 Build AI Reading List with DigitalOcean Functions and Mistral How To Concatenate Strings in Python How to Allow MySQL Remote Access Securely How To Install and Use Docker on Rocky Linux How To Build a Multi-Agent AI System with Docker Agent DSPy Use Cases: Build Optimized LLM Pipelines How To Submit AJAX Forms with jQuery Build an AI-Powered GPU Fleet Optimizer with the DigitalOcean AI Platform ADK Monitor GPU Utilization in Real Time: A Complete Guide Reduce File Size of Images in Linux - CLI and GUI methods Reduce PDF File Size in Linux: Tools and Methods How To Set Up a Private Docker Registry on Ubuntu How To Troubleshoot Terraform: Errors and Fixes How to Use Go Modules Python Multiprocessing Example: Process, Pool & Queue Convert Class Components to Functional Components with React Hooks How To Install and Configure Ansible on Ubuntu LLM Tokenizers Simplified: BPE, SentencePiece, and More How To Monitor System Authentication Logs on Ubuntu How to Use Traceroute and MTR to Diagnose Network Issues How to Deploy Postgres to Kubernetes Cluster Importing Packages in Go: A Complete Guide Create RAID Arrays with mdadm on Ubuntu How To Make an HTTP Server in Go How To Set Up Time Synchronization on Ubuntu How To Use Struct Tags in Go apt-key Deprecation: Add Repositories with GPG on Ubuntu Linux ps Command: 20 Real-World Examples Python struct.pack and struct.unpack for Binary Data Deadlock in Java: Examples, Detection, and Prevention How To Use Find and Locate to Search for Files on Linux Structured Resume Skill Extraction Using Mistral-7B Inference How to Use the Python Main Function How to Set Up NemoClaw on a DigitalOcean Droplet with 1-Click Build an End-to-End RAG Pipeline for LLM Applications From Single to Multi-Agent Systems: Key Infrastructure Needs Back Up Data to Object Storage Using Restic How to Generate Videos with LTX-2.3 on DigitalOcean GPU Droplets How To Install LAMP Stack (Apache, MySQL, PHP) on Ubuntu How to Download Files with cURL How To Use Variadic Functions in Go Generate UUIDs with uuidgen on Linux How To Use EJS to Template Your Node Application How to Install Node.js on Ubuntu (Step-by-Step Guide) MongoDB Indexes: Improve Query Performance with Node.js LLM Tool Calling with DigitalOcean AI Platform and Databases What are Text Diffusion Models? - An Overview Crafting a Game from Scratch with GPT-5.4 Building Long-Term Memory in AI Agents with LangGraph and Mem0 How To Install PHP 7.4 and Set Up a Local Development Environment on Ubuntu 20.04 Build a GraphQL API in Go to Upload Files to Spaces How To Lint and Format Code with ESLint in Visual Studio Code Train YOLO26 for Retail Object Detection on DigitalOcean GPUs How To Work with JSON in MySQL How to Use the JavaScript .map() Method Building a Scalable App with MongoDB Using DigitalOcean's MCP Server How to Create an SSH Key in Linux: Easy Step-by-Step Guide Measure MySQL Query Performance with mysqlslap How To Use *args and **kwargs in Python 3 Nemotron 3 helped me find the perfect dish rack? A2A vs MCP - How These AI Agent Protocols Actually Differ How To Install and Manage Supervisor Docker Container Images with Watchtower on Ubuntu Getting Started with Qwen3.5 Vision-Language Models How To Create a New Sudo-Enabled User on Ubuntu How to Use Ansible to Install and Set Up Docker on Ubuntu How To Enable Remote Desktop Protocol Using xrdp on Ubuntu 22.04 How To Convert a String to a List in Python How To Check If a String Contains Another String in Python How to Read a Properties File in Python Python Command Line Arguments: sys.argv, argparse, getopt Mastering Grep command in Linux/Unix: A Beginner's Tutorial Understanding Python Data Types How to Implement a Stack in C With Code Examples Python os.system() vs subprocess: Run System Commands How To Install and Use Docker Compose on Ubuntu How to Add and Delete Users on Ubuntu How To Order Query Results in Laravel Eloquent How To Define and Use Handlers in Ansible Playbooks How To Install and Use SQLite on Ubuntu How To Install and Use Homebrew on macOS How To Manage DateTime with Carbon in Laravel and PHP How To Install Git on Ubuntu How To Install and Secure Redis on Ubuntu How To Build and Install Go Programs on Linux Using ldflags to Set Version Information for Go Applications How To Build a Node.js Application with Docker How To Add JavaScript to HTML How To Reset Your MySQL or MariaDB Root Password How To Add Images in Markdown How To Set Up a Production Elasticsearch Cluster with Ansible How To Set Up a Firewall Using firewalld on CentOS Understanding Systemd Units and Unit Files How To Set Up Replication in MySQL How To Use the .htaccess File
How To Use Cron To Automate Recurring Tasks on Ubuntu
Shaun Lewis · 2026-06-16 · via DigitalOcean Community Tutorials

Introduction

Cron is a time-based job scheduler built into Ubuntu and other Unix-like systems. You use it to run commands and scripts automatically on a fixed schedule, which makes it the standard tool for backups, log rotation, cleanup jobs, and other recurring maintenance tasks. Cron has been part of Unix since Version 7 (1979) and remains the default scheduler on Ubuntu server installations.

In this guide, you will install and verify cron, read and write crontab expressions, set the environment variables that cron jobs depend on, schedule several real-world jobs, inspect cron logs to debug silent failures, and decide when a systemd timer is the better tool. The commands and file paths in this tutorial are verified against Ubuntu 22.04 and Ubuntu 24.04.

Key Takeaways

  • Cron is the default time-based job scheduler on Ubuntu; manage per-user jobs with the crontab command, not by editing files directly.
  • A cron expression has five time fields (minute, hour, day of month, month, day of week) followed by the command to run.
  • Edit jobs with crontab -e, list them with crontab -l, and remove them with crontab -r.
  • Cron runs jobs in a minimal environment, so use absolute paths and set PATH, SHELL, and MAILTO inside the crontab when needed.
  • Inspect cron activity with journalctl -u cron; most “job not running” issues trace back to relative paths, missing permissions, or environment differences.
  • Use cron for simple recurring schedules; reach for systemd timers when you need dependency ordering, missed-run handling, or per-job logging and resource limits.

Quick Start: Schedule Your First Cron Job

  1. Confirm cron is running: systemctl status cron.
  2. Open your crontab for editing: crontab -e.
  3. Add a job using the five-field schedule followed by the command, for example */5 * * * * /usr/local/bin/myscript.sh.
  4. Use absolute paths for every command and file the job references.
  5. Save and exit the editor (CTRL + X, then Y, then ENTER in nano). Cron loads the new schedule automatically.
  6. Verify the job is registered with crontab -l, and check that it ran with journalctl -u cron --since today.

Prerequisites

To follow this guide, you will need:

  • A machine running Ubuntu 22.04 or Ubuntu 24.04. This can be your local machine, a virtual machine, or a DigitalOcean Droplet.
  • A non-root user with sudo privileges. To set this up, follow the Initial Server Setup guide.
  • Basic familiarity with the Linux command line and a text editor such as nano.

Installing Cron

On most Ubuntu installations, cron is already present. To install or confirm it is installed, update the local package index first:

  1. sudo apt update

Then install cron with the following command:

  1. sudo apt install cron

Make sure the daemon is enabled at boot and running now:

  1. sudo systemctl enable --now cron
Synchronizing state of cron.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable cron

Confirm the service is active:

  1. systemctl is-active cron
active

Understanding How Cron Works

The Cron Daemon

Cron runs as a background service (cron.service) that wakes up every minute, reads the active crontab files, and runs any job whose schedule matches the current time. Because it runs as a service, you can inspect it with systemctl status cron and read its activity through journald, as covered in the Viewing and Debugging Cron Logs on Ubuntu section below.

The Crontab File

Cron jobs are recorded in a file called a crontab. Each user profile has its own crontab, stored under /var/spool/cron/crontabs/. These files are not meant to be edited directly. Instead, manage them with the crontab command, which validates your syntax and reloads the schedule for you.

System-Wide Cron Directories

Beyond per-user crontabs, Ubuntu ships a set of system-wide locations that run jobs as root:

Directory Default schedule
/etc/cron.hourly/ Once per hour
/etc/cron.daily/ Once per day
/etc/cron.weekly/ Once per week
/etc/cron.monthly/ Once per month
/etc/cron.d/ Custom schedules; file format mirrors /etc/crontab

Scripts placed in the cron.* drop-in directories must be executable and must not have a file extension. The /etc/cron.d/ directory accepts files in full crontab format, including a username field:

# /etc/cron.d/example-job
# Format: minute hour day month weekday username command
0 2 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

Use the drop-in directories for packaged or system-level maintenance scripts. Use per-user crontabs for application-level scheduling.

Cron Syntax Reference

The Five Time Fields

A cron expression is five schedule fields followed by the command to run:

Field Allowed Values Special Characters
Minute 0-59 * , - /
Hour 0-23 * , - /
Day of Month 1-31 * , - /
Month 1-12 or JAN-DEC * , - /
Day of Week 0-7 or SUN-SAT (0 and 7 both equal Sunday) * , - /

Together, a scheduled task is structured like this:

minute hour day_of_month month day_of_week command_to_run

The following expression runs curl https://www.example.com every Tuesday at 5:30 PM:

30 17 * * 2 curl https://www.example.com

There are also special characters you can include in the schedule component to streamline scheduling:

  • *: A wildcard representing “all.” A task scheduled with * * * * * ... runs every minute of every hour of every day.
  • ,: Breaks up values into a list. To run a task at the beginning and middle of every hour: 0,30 * * * * ....
  • -: Represents a range. To run a command for the first 30 minutes of every hour: 0-29 * * * * ....
  • /: Used with * to express a step value. To run a command every three hours: 0 */3 * * * ....

Note: Cron accepts any integer step value, but only steps that divide evenly into the field’s range produce a consistent interval. In the “hours” field the evenly dividing steps are 1, 2, 3, 4, 6, 8, and 12. A value like */5 is valid syntax but resets at midnight, leaving an uneven gap between the last run of one day and the first of the next.

Reading and Writing Crontab Expressions

Common schedule examples:

  • * * * * *: Run every minute.
  • 12 * * * *: Run 12 minutes after every hour.
  • 0,15,30,45 * * * *: Run every 15 minutes.
  • */15 * * * *: Run every 15 minutes (step syntax).
  • 0 4 * * *: Run every day at 4:00 AM.
  • 0 4 * * 2-4: Run every Tuesday, Wednesday, and Thursday at 4:00 AM.
  • 20,40 */8 * 7-12 *: Run at minutes 20 and 40 of every 8th hour, during the last 6 months of the year.

If you find any of this confusing, Cronitor provides a cron schedule expression editor named Crontab Guru that validates your expressions interactively.

Special Cron Strings

Cron supports shorthand strings in place of a numeric schedule. These are supported by the version of cron shipped with Ubuntu 22.04 and 24.04:

String Equivalent Expression Description
@reboot (none) Run once at daemon startup after system boot
@hourly 0 * * * * Run at the start of every hour
@daily 0 0 * * * Run at midnight every day
@weekly 0 0 * * 0 Run at midnight every Sunday
@monthly 0 0 1 * * Run at midnight on the first day of each month
@yearly 0 0 1 1 * Run at midnight on January 1st

The @reboot string runs its command once immediately after the cron daemon starts following a reboot. It is useful for starting user-level services or initialization scripts:

@reboot /home/sammy/scripts/init.sh >> /home/sammy/logs/init.log 2>&1

Note: Not all cron daemons can parse shorthand strings (particularly older versions). Confirm they work on your target system before relying on them in production. For startup jobs with strict ordering requirements, a systemd service unit is more reliable than @reboot.

Managing Your Crontab File

Use the crontab command to manage your scheduled jobs. It edits the crontab file for your user profile, validates syntax before saving, and reloads the schedule automatically. Do not edit the file under /var/spool/cron/crontabs/ directly. That path bypasses validation and can corrupt the crontab.

Opening the Crontab Editor

You can edit your crontab with the following command:

  1. crontab -e

If this is the first time you’re running the crontab command under this user profile, it will prompt you to select a default text editor to use when editing your crontab:

no crontab for sammy - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano        <---- easiest
  2. /usr/bin/vim.basic
  3. /usr/bin/vim.tiny
  4. /bin/ed

Choose 1-4 [1]: 

Enter the number corresponding to the editor of your choice. Alternatively, you could press ENTER to accept the default choice, nano.

After making your selection, you’ll be taken to a new crontab containing some commented-out instructions on how to use it:

# Edit this file to introduce tasks to be run by cron.
# 
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
# 
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
# 
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
# 
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
# 
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
# 
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command

When you run crontab -e in the future, it will bring up your crontab in this text editor automatically. Once in the editor, you can input your schedule with each job on a new line. Otherwise, you can save and close the crontab for now (CTRL + X, Y, then ENTER if you selected nano).

Note: On Linux systems, there is another crontab stored under the /etc/ directory. This is a system-wide crontab that has an additional field for which user profile each cron job should be run under. This tutorial focuses on user-specific crontabs, but if you wanted to edit the system-wide crontab, you could do so with the following command:

  1. sudo nano /etc/crontab

Listing Current Cron Jobs

If you’d like to view the contents of your crontab, but not edit it, you can use the following command:

  1. crontab -l

To list cron jobs for a different user (requires sudo):

  1. sudo crontab -u username -l

Removing the Crontab

You can erase your crontab with the following command:

Warning: The following command will not ask you to confirm that you want to erase your crontab. Only run it if you are certain that you want to erase it.

  1. crontab -r

This command will delete the user’s crontab immediately. However, you can include the -i flag to have the command prompt you to confirm that you actually want to delete the user’s crontab:

  1. crontab -r -i
crontab: really delete sammy's crontab? (y/n)

When prompted, you must enter y to delete the crontab or n to cancel the deletion.

Editing Another User’s Crontab

Note that if a user has sudo privileges, they can edit another user’s crontab with the following command:

  1. sudo crontab -u user -e

However, if cron.deny exists and user is listed in it and they aren’t listed in cron.allow, you’ll receive the following error after running the previous command:

The user user cannot use this program (crontab)

Setting Environment Variables in Cron

Why Cron Runs in a Minimal Environment

Cron jobs do not inherit your interactive shell environment. They run with a minimal set of variables: typically SHELL=/bin/sh, a short PATH such as /usr/bin:/bin, and a HOME set to the job owner’s home directory. This is the most common reason a command that works in your terminal fails under cron: a binary on your interactive PATH is not found, or a script relies on a variable that cron never sets.

To avoid this, use absolute paths for every command and file, and declare the variables your jobs need at the top of the crontab.

Setting PATH, SHELL, and MAILTO in the Crontab

Declare environment variables at the top of the crontab file, before any job lines:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/sammy
MAILTO="example@example.com"

* * * * * echo 'Run this command every minute'

SHELL sets the interpreter used to run commands. PATH controls where cron looks for binaries, which is why setting it explicitly resolves most “command not found” failures. HOME is the home directory the job runs in, not a search path for the cron binary. MAILTO directs job output to an email address; emailing requires a configured mail transfer agent such as Sendmail or Postfix on the server. Set MAILTO="" to disable email output entirely.

Note: Cron treats the percent sign (%) as a newline inside the command field. If a command contains a literal %, such as in a date format string, escape it as \%.

Capturing Output and Errors to a Log File

Relying on email for output is fragile because most servers do not have a mail transfer agent configured. The reliable pattern is to redirect both standard output and standard error to a log file. Append >> /path/to/logfile.log 2>&1 to the command, using an absolute path.

Before running any job that writes to a log file, make sure the destination directory exists:

  1. mkdir -p /home/sammy/logs
* * * * * /usr/local/bin/backup.sh >> /home/sammy/logs/backup.log 2>&1

Here >> appends standard output to the log file, and 2>&1 redirects standard error to the same destination so both successes and failures are captured. To discard output entirely and run a job silently, redirect to /dev/null instead:

* * * * * /usr/bin/php /var/www/example.com/sync.php > /dev/null 2>&1

Note: Log files written by cron jobs grow indefinitely unless rotated. For jobs that run frequently or produce verbose output, add a configuration file under /etc/logrotate.d/ to rotate and compress the log automatically. Alternatively, use a timestamped filename in the crontab command so each run writes a separate file:

0 2 * * * /usr/local/bin/backup.sh >> /home/sammy/logs/backup-$(date +\%Y\%m\%d).log 2>&1

Note the escaped \%: cron treats unescaped % as a newline.

Restricting Crontab Access

Two files control which users can schedule cron jobs: /etc/cron.allow and /etc/cron.deny. Neither file exists by default on Ubuntu. When both are absent, all users with shell access can use crontab. Creating either file activates the restriction:

  • If /etc/cron.deny exists, users listed in it cannot edit their crontab.
  • If /etc/cron.allow exists, only users listed in it can edit their crontab.
  • If both files exist and a user appears in both, cron.allow takes precedence.

For example, to restrict crontab access so only the user ishmael can schedule cron jobs, you can create an /etc/cron.allow file that lists allowed users:

echo ishmael | sudo tee /etc/cron.allow > /dev/null
sudo rm -f /etc/cron.deny
echo ishmael | sudo tee -a /etc/cron.allow

Use sudo tee (or sudo sh -c) when writing to files under /etc/; sudo echo ... >> file fails because the redirection happens in your unprivileged shell. Creating /etc/cron.allow restricts crontab access to only the users listed in that file.

The pipe to sudo tee -a is what makes this work: with sudo echo ALL >> /etc/cron.deny, the >> redirection runs in your unprivileged shell and fails with a permission error because only the echo runs as root. Piping to sudo tee -a performs the append as root. The first command locks out all users by appending ALL to cron.deny; the second grants the ishmael profile access by listing it in cron.allow, which takes precedence over cron.deny.

Practical Crontab Examples

These examples assume the referenced scripts exist, are executable (chmod +x), and begin with a shebang such as #!/bin/bash. They also assume the log directory exists. If you have not already created it, run:

  1. mkdir -p /home/sammy/logs

Note: Cron schedules use the system’s local timezone. To confirm what timezone your server is set to before scheduling time-sensitive jobs, run:

  1. timedatectl

If the server is set to UTC and you need jobs to run at a local time, either adjust the server timezone with sudo timedatectl set-timezone Region/City or run date -u to check current UTC time and calculate the offset manually. A job set to 0 9 * * 1 runs at 09:00 in whatever timezone timedatectl reports.

End-to-End Example: Write, Schedule, and Verify a Job

This example walks through the complete workflow: write a script, make it executable, schedule it, and confirm it ran.

Step 1: Write the script.

Create the directory for your scripts if it does not already exist, then open a new script file:

  1. mkdir -p /home/sammy/scripts
  2. nano /home/sammy/scripts/disk-report.sh

Add the following content:

#!/bin/bash
# disk-report.sh - appends a timestamped disk-usage snapshot to the log
LOGFILE="/home/sammy/logs/disk-report.log"
printf '\n=== %s ===\n' "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOGFILE"
df -h / >> "$LOGFILE"

Save and exit (CTRL + X, Y, ENTER).

Step 2: Make the script executable.

  1. chmod +x /home/sammy/scripts/disk-report.sh

Step 3: Test the script manually before scheduling it.

Always confirm the script runs correctly outside of cron first:

  1. /home/sammy/scripts/disk-report.sh
  2. cat /home/sammy/logs/disk-report.log
=== 2026-06-10 14:05:22 ===
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        25G  4.2G   20G  18% /

If the output appears correctly, the script is ready to schedule.

Step 4: Add the job to your crontab.

Open your crontab:

  1. crontab -e

To test that cron picks up the job, add it with a * * * * * schedule first (every minute). Change it to the final schedule once you have confirmed it runs:

* * * * * /home/sammy/scripts/disk-report.sh

Save and exit. Wait one to two minutes.

Step 5: Confirm the job ran.

Check the log file:

  1. cat /home/sammy/logs/disk-report.log

You should see a new timestamped entry. Also confirm cron triggered the job:

  1. journalctl -u cron --since "5 minutes ago"
Jun 10 14:06:01 hostname CRON[pid]: (sammy) CMD (/home/sammy/scripts/disk-report.sh)

Step 6: Update to the final schedule.

Once confirmed, open the crontab again and change the schedule. To run the report every day at 6:00 AM:

0 6 * * * /home/sammy/scripts/disk-report.sh

Run a Backup Script Every Night at 2:00 AM

0 2 * * * /usr/local/bin/backup.sh >> /home/sammy/logs/backup.log 2>&1

Clear a Temporary Directory Every Sunday at 3:00 AM

0 3 * * 0 /usr/bin/find /tmp/cache -type f -mtime +7 -delete

This removes files in /tmp/cache that are older than seven days.

Run a Script on Server Reboot

@reboot /usr/local/bin/startup.sh >> /home/sammy/logs/startup.log 2>&1

Run a Sync Job Every 15 Minutes

*/15 * * * * /usr/bin/php /var/www/example.com/sync.php >> /home/sammy/logs/sync.log 2>&1

Send a Weekly Report Every Monday at 8:00 AM

0 8 * * 1 /usr/local/bin/weekly-report.sh >> /home/sammy/logs/weekly-report.log 2>&1

For more on building the scripts these jobs run, see How To Write a Simple Shell Script on a VPS.

Viewing and Debugging Cron Logs on Ubuntu

Checking Cron Logs with journalctl

On Ubuntu 22.04 and 24.04, the reliable way to view cron activity is through journald, which is always present on systemd-based systems. View today’s cron entries:

  1. journalctl -u cron --since today
Jun 10 02:00:01 hostname CRON[pid]: (sammy) CMD (/usr/local/bin/backup.sh >> /home/sammy/logs/backup.log 2>&1)

To follow cron activity live:

  1. journalctl -u cron -f

Reading /var/log/syslog for Cron Entries

If rsyslog is installed, cron also writes to /var/log/syslog. Filter for cron entries:

  1. grep CRON /var/log/syslog

Note: Minimal Ubuntu 24.04 installations may not include rsyslog, in which case /var/log/syslog will not exist. Use journalctl -u cron instead, or install rsyslog with sudo apt install rsyslog.

Common Reasons Cron Jobs Fail Silently

Once you have confirmed in the logs that a job is triggering (the CMD line is present), but the job is not producing the expected result, work through the Common Mistakes checklist. The most frequent causes are relative paths, missing execute permission, environment differences, and unescaped percent signs. Each is covered there with the specific fix.

If the CMD line is absent from the logs entirely, the job is not triggering. Check that the cron daemon is running (systemctl status cron), that your crontab expression is valid (crontab.guru), and that the user has not been blocked by /etc/cron.allow or /etc/cron.deny.

Cron vs. Systemd Timers: When to Use Each

Key Differences

Both cron and systemd timers schedule recurring work, but they differ in capability:

Criteria Cron Systemd Timer
Setup complexity Single crontab line Two unit files (.timer and .service)
Logging Manual redirection to a file Captured in journald automatically
Dependency ordering Not supported Supported via unit dependencies
Missed-run handling Skipped if system was off Persistent=true runs missed jobs at next boot
Randomized start delay Not native RandomizedDelaySec available
Resource limits per job Not supported Inherited from the service unit (cgroups)
Portability Available on virtually all Unix-like systems Linux with systemd only

Decision Guidance

Use cron when the task is a simple recurring command, portability matters, or you want the lowest setup overhead. Choose a systemd timer when you need built-in logging through journald, dependency ordering, missed-run recovery on a machine that is not always on, or per-job resource control.

Minimal Systemd Timer Example

For comparison, the equivalent of 0 2 * * * /usr/local/bin/backup.sh as a systemd timer requires two files.

The service unit at /etc/systemd/system/backup.service:

[Unit]
Description=Daily backup job

[Service]
Type=oneshot
User=sammy

Group=sammy

ExecStart=/usr/local/bin/backup.sh
StandardOutput=append:/home/sammy/logs/backup.log
StandardError=append:/home/sammy/logs/backup.log

The timer unit at /etc/systemd/system/backup.timer:

[Unit]
Description=Run backup daily at 2 AM

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable and start the timer:

  1. sudo systemctl enable --now backup.timer

Persistent=true means the job runs at the next opportunity if the system was off at the scheduled time. Cron silently skips that run. Use journalctl -u backup.service to view logs without any redirection setup.

For workloads that outgrow both, consider a dedicated scheduler such as Celery for distributed task queues or Apache Airflow for pipeline orchestration.

To configure timers, see Understanding Systemd Units and Unit Files.

Common Mistakes

  • Using ~ in crontab paths. Cron does not expand ~ to the home directory. Always use the full absolute path: /home/sammy/.
  • Forgetting to make the script executable. Cron cannot run a script without the execute bit set. Run chmod +x /path/to/script.sh before scheduling it.
  • Omitting the shebang. A script called by cron needs an interpreter line on the first line, such as #!/bin/bash or #!/usr/bin/env python3. Without it, the shell does not know how to execute the file.
  • Assuming cron shares your interactive environment. Cron runs with a minimal PATH (/usr/bin:/bin). Binaries outside that path are not found unless you set PATH explicitly at the top of the crontab or use absolute paths for every command.
  • Unescaped percent signs in the crontab command field. Cron reads an unescaped % as a newline. Escape literal percent signs as \%. This applies only in the crontab command field; scripts called by cron can use % freely.
  • Forgetting output redirection. Without >> /path/to/log 2>&1, cron discards all output silently unless a mail transfer agent is configured.
  • Using sudo inside a user crontab. A user crontab already runs as that user. For root-level tasks, edit the root crontab with sudo crontab -e rather than prepending sudo to commands inside a user crontab.
  • Editing /var/spool/cron/crontabs/ directly. These files must be managed through crontab -e. Direct edits bypass syntax validation and can corrupt the crontab.
  • Forgetting crontab -r -i. Running crontab -r deletes the crontab immediately with no confirmation. Use crontab -r -i to prompt before deletion.
  • Using sudo echo >> /etc/cron.deny. The >> redirection runs in the unprivileged shell and fails with a permission error, because only echo runs as root. Use echo value | sudo tee -a /etc/cron.deny instead.
  • No protection against overlapping runs. Cron has no built-in locking. If a job is still running when the next scheduled run triggers, cron starts a second instance. For jobs that must not overlap (database exports, slow backups), use a lock file or flock:
*/5 * * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /home/sammy/logs/backup.log 2>&1

flock -n exits immediately if the lock is already held, so the second instance skips rather than stacking.

Frequently Asked Questions

How Do I Open and Edit My Crontab File on Ubuntu?

Run crontab -e in your terminal. This opens your user-level crontab in the default editor (usually nano on Ubuntu). Each line in the file represents one scheduled job.

How Do I List All Cron Jobs for the Current User on Ubuntu?

Run crontab -l to print all scheduled jobs for the current user. To list another user’s jobs, run sudo crontab -u username -l.

How Do I Disable a Cron Job Temporarily Without Deleting It?

Open the crontab with crontab -e and add a # character at the start of the job line to comment it out:

# 0 2 * * * /usr/local/bin/backup.sh >> /home/sammy/logs/backup.log 2>&1

Cron ignores commented lines. Remove the # to re-enable the job. This is the standard approach for pausing a scheduled job during maintenance without losing the expression.

Why Is My Cron Job Not Running on Ubuntu?

Run journalctl -u cron --since today. If the CMD line for your job appears in the output, the job triggered and the issue is in the script or its environment. If the CMD line is absent, the job is not firing at all. See Viewing and Debugging Cron Logs on Ubuntu for the diagnostic workflow and Common Mistakes for the full failure-mode checklist.

How Do I Check Cron Logs on Ubuntu?

On Ubuntu 22.04 and 24.04, run journalctl -u cron --since today to view recent cron activity; this works on every systemd-based system. If rsyslog is installed, you can also run grep CRON /var/log/syslog. Log entries show whether a job was triggered and whether it produced output or errors.

What Is the Difference Between crontab -e and /etc/crontab?

crontab -e edits the per-user crontab, and jobs run as that user. /etc/crontab is the system-wide crontab and includes an extra field specifying the user each job runs as. For application-level scheduling, per-user crontabs are preferred.

What Does @reboot Do in a Crontab?

@reboot runs its command once after the cron daemon starts at boot. For a usage example and compatibility notes, see Special Cron Strings.

Does Cron Run When I Am Not Logged In?

Yes. Cron is a system daemon that runs continuously in the background regardless of whether any user is logged in. Jobs scheduled in a user’s crontab run at the specified time as long as the machine is on and the cron service is running. This is one of the primary reasons cron is used for server maintenance: the task runs whether or not an administrator is connected.

Should I Use Cron or Systemd Timers on Ubuntu?

For simple recurring tasks where portability and simplicity matter, cron is the practical choice. Systemd timers are preferable when you need dependency management, logging through journald without extra configuration, missed-run recovery, or per-job resource limits.

How Do I Redirect Cron Job Output to a Log File?

Append >> /absolute/path/to/logfile.log 2>&1 to the command. This sends both standard output and standard error to the file. Without it, cron tries to email output to the local user, which is often not configured on servers.

Conclusion

In this guide, you installed and verified cron, learned the five-field schedule syntax and the special shorthand strings, and managed jobs with the crontab command. You also set the environment variables cron jobs depend on, redirected their output to log files, and worked through practical scheduling examples covering backups, cleanup jobs, and boot-time initialization.

You then inspected cron activity with journalctl and stepped through the most common reasons cron jobs fail silently. With that troubleshooting checklist, you can diagnose a scheduled job that is not behaving as expected rather than guessing at the cause.

To go further, combine cron with shell scripts to automate larger workflows. For example, you could Automate backups with cron. When a job outgrows a fixed schedule, revisit the Cron vs. Systemd Timers section to decide whether a timer fits better.

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.