Cron Jobs
By the end of this lesson you'll be able to read and write cron expressions, schedule a PHP CLI script to run automatically, log its output, and stop two runs from colliding — the toolkit behind every nightly backup and email digest.
Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Cron Syntax: The Five Fields
A cron job is a command your server runs automatically on a schedule. That schedule is written as five space-separated fields followed by the command: minute hour day-of-month month day-of-week command . An asterisk ( * ) means "every value", */5 means "every 5th", a dash like 1-5 is a range , and a comma like 1,15 is a list . Learn to read these six lines and you can express almost any schedule.
Cron itself prints nothing — it just launches the command at the right moment. The fields only decide when . A common gotcha: the day-of-month and day-of-week fields are OR-ed together, so 0 0 13 * 5 runs on the 13th and on every Friday, not only Friday the 13th.
2️⃣ Installing a Job with crontab -e
Each user has their own crontab (cron table). You edit it with crontab -e , which opens an editor; the moment you save, the schedule is live. The command in a cron line is run by a bare shell, so you give the full path to PHP and the full path to your script : /usr/bin/php /var/www/app/backup.php . Cron throws away your script's output unless you capture it — so always redirect it to a log file with > > file.log 2 > &1 (append stdout, and send errors to the same place).
3️⃣ The PHP CLI Script Cron Runs
The script cron launches is ordinary PHP, but run from the command line rather than a browser. That mode is called the CLI SAPI (Server API), and php_sapi_name() returns "cli" there. There is no web page, so everything you echo goes to stdout — which the cron line redirects into your log file. Finish with a clear exit code : exit(0) for success, anything else for failure.
Because each line is timestamped with date('Y-m-d H:i:s') , the log tells you not just that the job ran but when — the first thing you'll want when something breaks at 2 AM.
4️⃣ Preventing Overlap with flock
What happens if a job scheduled */5 * * * * sometimes takes seven minutes? Cron starts the next copy on top of the first, and now two runs fight over the same data. A file lock stops this: the first run grabs an exclusive lock; any later run that can't grab it simply exits. You can do this in the cron line with the flock command (shown in section 2) or inside PHP with the flock() function below.
5️⃣ Your Turn: Write a Cron Line
Now you write the schedule. The line below is almost complete — fill in each ___ using the field order and the 👉 hints, then check it against the Output panel.
One more — this time the PHP script. Cron will redirect its output to a log, so give every line a timestamp . Fill in the blanks and run it.
Common Errors (and the fix)
- "php: command not found" (or the script just never runs) — cron uses a bare PATH and a different working directory than your login shell. Use absolute paths for both PHP and the script: /usr/bin/php /var/www/app/job.php (find PHP's path with which php ), and don't rely on relative paths or shell aliases.
- The job runs but reads the wrong files / env vars are missing — cron doesn't load your ~/.bashrc or shell profile, so variables you set there are absent. Set them at the top of the crontab ( PATH=... , APP_ENV=... ) or load them inside the script, and cd to the project directory in the command.
- Two copies clobber each other — a long run overlapped the next scheduled start. Wrap the command in flock -n /tmp/job.lock in the crontab, or call flock($handle, LOCK_EX | LOCK_NB) in PHP so the second run exits instead of piling up.
- It fails silently and you can't tell why — you didn't capture output. Add > > /var/log/app/job.log 2 > &1 to the cron line so both normal output and errors land in a file, then read it with tail -f .
- "Call to undefined function" for a web-only feature — you assumed the web SAPI. From cron PHP runs as cli : there's no $_SESSION , no header() that a browser will see, and a different php.ini may apply. Write CLI scripts to read arguments and print to stdout, not to expect a request.
Pro Tips
- 💡 Test the command by hand first. Run the exact line ( /usr/bin/php /var/www/app/job.php ) in a terminal before trusting cron with it — most "cron bugs" are path or permission bugs that show up immediately when run directly.
- 💡 Log a heartbeat, then alert on its absence. Have the job write a timestamp on success; if the log goes quiet, you know it stopped — far better than waiting for a user to notice.
- 💡 Prefer the Laravel scheduler or systemd timers for anything non-trivial. They keep timing logic in version control and give you proper logs ( journalctl ) instead of a crontab nobody remembers editing.
6️⃣ Alternatives: systemd Timers & the Laravel Scheduler
Plain crontab is everywhere, but two alternatives solve its weak spots. systemd timers pair a .timer unit (when) with a .service unit (what), giving you centralised logging via journalctl -u myjob , dependencies, and easy enable / disable . The Laravel scheduler flips the model: you define all schedules in PHP and add a single cron line that runs php artisan schedule:run every minute.
📋 Quick Reference — Cron Fields & Commands
Field / Token
Range / Example
Meaning
1st field
minute (0-59)
Minute of the hour
2nd field
hour (0-23)
Hour, 24-hour clock
3rd field
day of month (1-31)
Day number in the month
4th field
month (1-12)
Month of the year
5th field
day of week (0-6)
0 = Sunday … 6 = Saturday
*
* * * * *
Every value (here: every minute)
*/n a-b a,b
*/5 1-5 1,15
Every nth · range · list
crontab -e / -l
crontab -e
Edit / list your cron jobs
> > log 2 > &1
> > job.log 2 > &1
Append output + errors to a log
flock
flock -n /tmp/j.lock cmd
Prevent overlapping runs
Frequently Asked Questions
Mini-Challenge: A Self-Protecting Heartbeat
No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This combines the lock and the timestamped log into one real cron-ready script.
🎉 Lesson Complete!
- ✅ A cron schedule is five fields — minute, hour, day-of-month, month, day-of-week — then the command
- ✅ crontab -e installs jobs; crontab -l lists them
- ✅ Run PHP from cron with absolute paths : /usr/bin/php /path/script.php
- ✅ Capture output and errors with > > file.log 2 > &1 — silent jobs are undebuggable
- ✅ Stop overlaps with flock (in the cron line or via PHP's flock() )
- ✅ Cron's bare environment is the #1 gotcha — set PATH /env explicitly, and remember PHP runs as cli , not the web SAPI
- ✅ For more, reach for systemd timers or the Laravel scheduler
- ✅ Next lesson: Environment Management — store config and secrets securely with .env files
Practice quiz
What is the correct order of the five cron fields?
- hour, minute, day, month, year
- day-of-week, month, day, hour, minute
- minute, hour, day-of-month, month, day-of-week
- second, minute, hour, day, month
Answer: minute, hour, day-of-month, month, day-of-week. Read left to right: minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), day-of-week (0-6), then the command.
What does the cron expression '0 9 * * 1-5' mean?
- 09:00, Monday to Friday
- Every 9 minutes on weekends
- 9 PM on the 1st to 5th of the month
- Midnight every 9 days
Answer: 09:00, Monday to Friday. Minute 0, hour 9, any day-of-month, any month, day-of-week 1-5 (Mon-Fri) — so 9 AM on weekdays.
What does the token '*/5' mean in the minute field?
- Exactly minute 5
- Minutes 1 through 5
- Five times total
- Every 5th minute
Answer: Every 5th minute. * means 'every', */5 means 'every 5th', a-b is a range, and a,b is a list.
Why must you use absolute paths in a crontab command?
- Cron only accepts uppercase paths
- Cron runs with a bare environment, so 'php' or './script.php' may not resolve
- Relative paths are a syntax error
- Absolute paths run faster
Answer: Cron runs with a bare environment, so 'php' or './script.php' may not resolve. Cron uses a minimal PATH and a different working directory, so use /usr/bin/php /var/www/app/script.php — full paths to both.
How do you capture both normal output and errors from a cron job to a log file?
- >> file.log 2>&1
- | file.log
- > file.log only
- 2>file.log only
Answer: >> file.log 2>&1. >> appends stdout to the log and 2>&1 sends stderr to the same place. Without this, a failing job is silent and undebuggable.
Which command opens your personal crontab in an editor?
- crontab -l
- crontab -r
- crontab -e
- cron --edit
Answer: crontab -e. crontab -e edits (and installs on save), crontab -l lists, and crontab -r removes ALL of the user's jobs.
How do you stop two copies of a cron job from running at the same time?
- Schedule it less often and hope
- Wrap it in flock with a lock file (or call flock() in PHP)
- Run it as root
- Add MAILTO to the crontab
Answer: Wrap it in flock with a lock file (or call flock() in PHP). flock -n /tmp/job.lock (or PHP's flock($handle, LOCK_EX | LOCK_NB)) makes a second run exit instead of overlapping.
In a PHP CLI script, what does php_sapi_name() return when run from cron?
- 'fpm'
- 'cron'
- 'apache'
- 'cli'
Answer: 'cli'. From the command line (and cron) PHP runs as the CLI SAPI, so php_sapi_name() returns 'cli'.
Which exit code signals a successful cron run?
- exit(1)
- exit(0)
- exit(200)
- exit(-1)
Answer: exit(0). exit(0) means success; any non-zero code is recorded by cron as a failed run.
Which is a modern alternative to plain crontab on a single server?
- An infinite PHP while-loop
- A browser refresh tag
- systemd timers (a .timer plus a .service unit)
- A Composer script
Answer: systemd timers (a .timer plus a .service unit). systemd timers give logging via journalctl, dependencies, and easy enable/disable; the Laravel scheduler keeps timing logic in PHP with one cron line.
Continue this course
- Previous: Search Features
- Next: Environment Management