Cron Jobs Explained: How Linux Runs Tasks on a Schedule

A cron job is a task your server runs automatically on a schedule — every minute, every night, every Monday. Learn how cron works, how to read and write the famous five-field schedule, where the gotchas hide, and when to reach for systemd timers instead.

Published September 26, 202611 min readBy ACY Partner Indonesia
Cron jobs — running Linux tasks automatically on a schedule
300 × 250Ad Space AvailablePlace your ad here

Some work on a server can’t wait for a human to remember it. Backups need to run every night. Old log files need clearing every week. A report needs to be emailed every Monday at 8 a.m. You could set an alarm and do it by hand forever — or you could hand the job to cron, the part of Linux whose entire purpose is to run things on a schedule, quietly, without you watching.

Cron is one of those tools that has been around for decades and is still everywhere. Once you understand its one slightly cryptic line — the five-field schedule — you can automate almost any repetitive task on a server. Let’s take it apart piece by piece, starting from zero.

What a cron job actually is

A cron job is a command (or script) plus a schedule that tells the system when to run it. The schedule and the command live together on a single line, and the system stores a list of those lines for you.

Behind the scenes there’s a small background program — a service — called the cron daemon (often shown as cron or crond). It’s part of the family of always-running background programs we covered in processes and services. The daemon wakes up roughly every minute, looks at every scheduled line it knows about, and asks a simple question: is it time to run this one yet? If the answer is yes, it runs the command. If not, it goes back to sleep and checks again a minute later.

That’s the whole idea. Cron doesn’t do the work itself — it’s a patient clock-watcher that launches your commands at your chosen times. The list of those scheduled lines is called a crontab, short for “cron table.”

The daemon has to be running

Cron only fires if the cron service is actually alive on the machine. On most servers it starts automatically at boot and stays up, so you rarely think about it. But if your scheduled jobs mysteriously never run, the very first thing to check is whether the cron daemon is running at all — a stopped service means nothing gets scheduled, no matter how perfect your crontab looks.

Reading the five-field schedule

The famous (and famously confusing) part of cron is the schedule itself. It’s five numbers in a row, each describing one unit of time, from smallest to largest:

 ┌───────────── minute        (0 - 59)
 │ ┌─────────── hour          (0 - 23)
 │ │ ┌───────── day of month  (1 - 31)
 │ │ │ ┌─────── month         (1 - 12)
 │ │ │ │ ┌───── day of week   (0 - 6, Sunday = 0)
 │ │ │ │ │
 *  *  *  *  *   command-to-run

Read it left to right: minute, hour, day-of-month, month, day-of-week, then the command. An asterisk (*) in any field means “every value” — every minute, every hour, and so on.

So the simplest schedule of all is five asterisks:

* * * * * /path/to/script.sh

That reads as “every minute of every hour of every day” — the job runs once a minute, forever. Useful for testing; rarely what you want in production.

Let’s make it concrete. Here are real schedules you’d actually use, with a plain-English reading for each:

# At 02:30 every night — a classic time for backups
30 2 * * * /opt/scripts/backup.sh

# Every 15 minutes, around the clock
*/15 * * * * /opt/scripts/health-check.sh

# At 08:00 every Monday — Monday = day-of-week 1
0 8 * * 1 /opt/scripts/weekly-report.sh

# On the 1st of every month at midnight
0 0 1 * * /opt/scripts/monthly-cleanup.sh

# Every hour, on the hour
0 * * * * /opt/scripts/sync.sh

The trick that unlocks everything: when a field is *, it places no restriction. The job runs whenever the non-asterisk fields all match. In 30 2 * * *, only the minute (30) and hour (2) are pinned, so it matches once a day — at 02:30, every day, every month, regardless of weekday.

The shortcuts: steps, lists, and ranges

You don’t have to write a single number in each slot. Cron understands a few small patterns that make schedules far more expressive:

  • */n — a step. */15 in the minute field means “every 15 minutes” (at :00, :15, :30, :45). */2 in the hour field means “every 2 hours.”
  • a,b,c — a list. 0 9,12,17 * * * runs at 9 a.m., noon, and 5 p.m. — three specific hours.
  • a-b — a range. 0 9-17 * * 1-5 runs every hour from 9 a.m. to 5 p.m., Monday through Friday — a “business hours, weekdays” schedule.

You can combine them too. 0 8-18/2 * * * means “every 2 hours between 8 a.m. and 6 p.m.” Read a complicated schedule one field at a time and it always resolves cleanly.

Don't trust your gut — verify the schedule

Cron expressions are easy to misread, and a wrong one can run far too often (or never). Before trusting a schedule on a real server, double-check what it actually means in plain language: say each field out loud, or write the human reading in a comment right next to the line, like the examples above. A schedule that runs every minute when you meant every month can quietly hammer your server for days before anyone notices.

Editing your crontab

Each user on the system has their own personal crontab. You don’t edit the file directly with a text editor — instead you use the crontab command, which opens your list safely and reloads it for you when you’re done.

# Open YOUR crontab for editing
crontab -e

# List (print) your current crontab without editing
crontab -l

# Wipe your entire crontab — careful, no confirmation
crontab -r

The first time you run crontab -e, it may ask which text editor to use; pick whichever you’re comfortable with. Inside, each non-blank, non-comment line is one job. Lines starting with # are comments and ignored — perfect for labeling what each job does.

A clean, well-documented crontab looks like this:

# Nightly database backup at 02:30
30 2 * * * /opt/scripts/backup-db.sh

# Clear temporary files every Sunday at 04:00
0 4 * * 0 /opt/scripts/clean-temp.sh

# Renew certificates twice a day (recommended cadence)
0 0,12 * * * /opt/scripts/renew-certs.sh

There’s also a system crontab at /etc/crontab and small drop-in folders like /etc/cron.d/, plus convenience directories named /etc/cron.daily/, /etc/cron.weekly/, and so on. Anything you place in cron.daily runs once a day without you writing a schedule at all — the system handles the timing. Those are handy for system-wide tasks, but for your own jobs, your personal crontab -e is usually the cleanest place to start.

How a cron job actually runs (and why it surprises people)

Here’s the part that trips up almost everyone the first time. When cron runs your command, it does not run it the same way your interactive shell does. It runs in a stripped-down environment:

 your terminal session              cron's environment
 ─────────────────────              ───────────────────
 full PATH (lots of dirs)           tiny PATH (often just /usr/bin:/bin)
 your env variables loaded          almost no env variables
 current dir = wherever you are     current dir = your home folder
 your shell's config sourced        config files NOT sourced

The practical fallout: a script that works perfectly when you type it by hand can silently fail under cron. The two usual culprits are:

  1. Commands “not found.” Your shell knows where python, node, or pg_dump live because your PATH is rich. Cron’s PATH is bare, so it can’t find them. The fix is to use full absolute paths to every program and file in your job — /usr/bin/python3 /opt/scripts/job.py, not just python3 job.py. (If you need a refresher on what PATH even is, see environment variables.)
  2. Missing environment variables. If your script depends on a variable that’s normally set by your shell login, cron won’t have it. Set what you need explicitly inside the script, or define it at the top of the crontab.

The single most reliable habit: point cron at a script, and make that script set up its own world — absolute paths, its own variables, its own working directory. Then cron only has to launch one thing, and the script takes care of the rest.

Capturing output, or it disappears

By default, anything your job prints — normal output and error messages — gets mailed to the user, if mail is even configured. On a typical server it isn’t, so that output simply vanishes. When a job fails at 3 a.m. and you have no idea why, this is usually the reason: the error existed, but nobody caught it.

The fix is to redirect the output to a log file yourself:

# Send normal output AND errors to a log file
30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

Two pieces are doing the work here. >> /var/log/backup.log appends the normal output to a log file (the double >> means append, so you keep history instead of overwriting it). The 2>&1 says “send errors to the same place as normal output,” so failures land in the log too. With that one addition, a silent, mysterious job becomes one you can actually debug — just read the log.

A job that never logs is a job you can't trust

Running a scheduled task with no logging is flying blind. You won’t know if it ran, if it succeeded, or if it’s been quietly failing for a month. Always redirect output somewhere you can read it, at least while a new job is young. The few extra characters at the end of the line are the difference between “I can see exactly what happened” and “I have no idea.”

When to reach for systemd timers instead

Cron is wonderful for simple, time-based jobs, and it isn’t going anywhere. But on modern Linux systems there’s a second scheduler worth knowing about: systemd timers. They’re part of the same systemd system that manages services (covered in systemd basics), and they trade a little extra setup for some real advantages:

  • Built-in logging. A timer’s output flows into the system journal automatically, so you can read it with the standard journal tools — no manual redirect needed.
  • Catch-up runs. A timer can be told to run a job it missed because the server was switched off when it was due. Cron just skips a missed run entirely.
  • Dependencies and conditions. A timer can wait until the network is up, or only run if certain conditions hold — things cron can’t express.

The honest rule of thumb: for a quick “run this script every night” task, plain cron is faster to set up and perfectly fine. For something more important — where you care about missed runs, clean logs, or ordering — a systemd timer is the more robust choice. Both have their place; knowing both means you pick the right tool instead of forcing one to do everything.

Why cron is worth learning

Automation is one of the things that separates a server you babysit from a server that takes care of itself. Cron is the oldest, simplest, most universal way to make that happen, and you’ll meet it on practically every Linux machine you ever touch. Backups, cleanups, report generation, certificate renewal, health checks — the unglamorous, essential chores of running a server are very often a cron job behind the scenes.

The five-field schedule looks cryptic for about ten minutes, and then it never confuses you again. Learn to read it, get into the habit of using absolute paths and logging your output, and you’ve got a dependable little robot that does the boring work while you sleep.

Wrapping up

Here’s everything in one place:

  • A cron job is a command plus a schedule that tells the system when to run it automatically.
  • The cron daemon is a background service that wakes every minute and runs any job whose time has come.
  • The schedule has five fields — minute, hour, day-of-month, month, day-of-week — followed by the command. * means “every value.”
  • Steps (*/n), lists (a,b,c), and ranges (a-b) make schedules expressive: every 15 minutes, weekdays 9–5, the 1st of the month, and so on.
  • Edit your jobs with crontab -e, list them with crontab -l. Comment each line so its purpose is obvious.
  • Cron runs in a bare environment — use absolute paths, set the variables you need, and redirect output to a log (>> file 2>&1) so failures aren’t invisible.
  • For missed-run catch-up, automatic logging, and conditions, consider a systemd timer instead.

With scheduled tasks under your belt, you’ve now seen most of the day-to-day toolkit for running a Linux server: navigating it, securing it, moving files onto it, and now teaching it to do its own repetitive work on a clock.

Tags:linuxcronautomationserverbeginner
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

SSL and encryption at rest — data protected in transit and on disk
Server / Server Security

SSL and Encryption at Rest: Protecting Data Both in Transit and on Disk

Encryption protects your data in two places: while it travels the network (in transit) and while it sits on disk (at rest). Learn the difference, why you need both, how TLS, full-disk encryption, and key management actually fit together, and the practical mistakes to avoid — explained from the.

Nov 9, 202613 min read
Securing ports and services — closing the doors on a server you don't use
Server / Server Security

Securing Ports and Services: Closing the Doors You Don't Use

Every open port on a server is a door someone could try to walk through. Learn what ports and services really are, why an exposed service is your biggest risk, and the simple discipline of closing everything you don't need — explained from the ground up.

Nov 8, 202611 min read
Principle of least privilege — granting only the minimum access each user and process needs
Server / Server Security

The Principle of Least Privilege: Give Everything Only the Access It Truly Needs

Least privilege is the quiet rule behind almost every solid security setup: every user, process, and key gets the minimum access required to do its job, and nothing more. Learn what it means, why it limits the blast radius of any breach, and how to apply it across users, services, files, and keys.

Nov 7, 202613 min read
Fail2ban and intrusion basics — watching logs and automatically banning repeated abusers
Server / Server Security

Fail2ban and Intrusion Basics: Automatically Banning the Bots Hammering Your Server

Attackers don't stop after one failed guess — they keep hammering, thousands of times a day. Learn how intrusion attempts actually look, what fail2ban does, how it watches your logs and bans abusers automatically, and how to set it up sensibly without locking yourself out — explained from zero.

Nov 6, 202613 min read
Keeping software updated — closing known security holes before attackers use them
Server / Server Security

Keeping Software Updated: The Boring Habit That Stops Most Server Breaches

Most servers don't get hacked through clever new attacks — they get hacked through old, known holes that a simple update would have closed. Learn why updates matter, what to update, how to do it safely without breaking things, and how to make it a habit instead of a panic.

Nov 5, 202610 min read
Firewall configuration — a default-deny rule set that opens only the ports you need
Server / Server Security

Firewall Configuration: Setting Up Default-Deny Rules Without Locking Yourself Out

Knowing what a firewall is and actually configuring one well are two different skills. Learn how to write a default-deny rule set, order rules correctly, allow your own access first, handle IPv6 and cloud security groups, and test before you commit — a practical, vendor-neutral guide from zero.

Nov 4, 202615 min read