Task Scheduling on Linux: From Raw crontab to the Laravel Scheduler
A cron line is five fields: minute, hour, day, month, weekday. Instead of stuffing crontab with dozens of fragile lines, put one line that runs schedule:run every minute and manage all your jobs from code with withoutOverlapping and onOneServer.
Task scheduling on Linux rests on cron, a service that runs commands at set times. Each task is a line of five time fields followed by the command: minute (0-59), hour (0-23), day of month (1-31), month (1-12), day of week (0-6 where Sunday is zero). An asterisk means "every value," so the line 0 3 * * * runs the command at 3 AM every day, and */5 * * * * runs it every five minutes. You edit your schedule with crontab -e and view it with crontab -l. The trouble is that stuffing crontab with dozens of lines quickly becomes a fragile mess: forgotten absolute paths, missing environment variables, and jobs overlapping on top of each other. The cleaner answer in a Laravel application is a single cron line that runs php artisan schedule:run every minute, then you define all your jobs in code inside the schedule file where you get clearer readability, an overlap lock via withoutOverlapping, and a guarantee against duplication across servers via onOneServer. This article explains the raw cron line structure, the environment and path trap, why one job is cleaner, and how to actually verify your job is running through logs and locks.
The cron Line Structure and How to Read It
The five fields read from the left: minute, then hour, then day of month, then month, then day of week. Examples cement the understanding: 30 2 * * * means 2:30 AM daily, 0 0 1 * * means midnight on the first of each month, and 0 9 * * 1 means 9 AM every Monday. The */n token means "every n," a comma lists values (1,15 = the 1st and 15th), and a dash gives a range (1-5 = Monday through Friday). A common mistake is mixing day of month and day of week, because if you set both to specific values, cron runs the task when either matches, not both, behavior that surprises many.
| cron line | When it runs |
|---|---|
| * * * * * | Every minute |
| */15 * * * * | Every 15 minutes |
| 0 * * * * | Top of every hour |
| 0 3 * * * | 3 AM daily |
| 0 0 * * 0 | Midnight every Sunday |
The Environment and Absolute Path Trap
The biggest reason cron jobs fail silently is that cron runs in a near-empty environment, inheriting neither your full PATH nor your session variables, so a command that works perfectly when you type it by hand in the terminal fails under cron because it cannot find php or composer on the path. The rule: always use absolute paths for everything. Instead of php artisan ... write the full path to PHP and the project, such as /usr/bin/php /home/site/artisan schedule:run. If a job depends on environment variables, define them explicitly at the top of the crontab file or load the environment file inside the script itself. Always redirect output to a log so you can see it later, by appending >> /home/site/storage/logs/cron.log 2>&1 at the end of the line, capturing both output and errors instead of letting them vanish into the void.
Why One Line Is Cleaner Than Dozens
The Laravel Scheduler flips the equation: instead of dozens of crontab lines, you place just one line on the server:
* * * * * /usr/bin/php /home/site/artisan schedule:run >> /dev/null 2>&1
This line wakes Laravel every minute, so it reads your schedule defined in code and decides which task is due. The benefit is that your entire schedule now lives in one file under version control (Git), which you view, review, and deploy like the rest of your code, without touching crontab on the server again. You write daily() for a daily task, everyFiveMinutes() for one every five minutes, plus hourly(), weeklyOn(), and cron('0 3 * * *') if you want the raw cron expression for a special case. To add a new task you write it in code and deploy, with no SSH and no error-prone manual crontab editing. This separation between the "timing engine" (the single cron line) and the "task definitions" (the code) is the essence of cleanliness.
withoutOverlapping and onOneServer
Two problems appear in production. The first is overlap: a task takes three minutes while scheduled every minute, so a second instance starts before the first finishes and copies pile up until the server is saturated. The fix is calling withoutOverlapping() on the task, so Laravel places a lock that prevents a new instance from starting before the running one finishes. The second is multiple servers: if your application runs on several servers behind a load balancer, each one runs schedule:run and a single task may execute several times, sending the same email or issuing the same invoice repeatedly. The fix is onOneServer(), so the servers agree via a shared lock (Redis or the database) that only one executes it. These two tools turn scheduling from fragile into reliable.
How to Verify a Task Actually Runs
Silent scheduling is dangerous: you think your task runs while it has been stopped for a week. Verify three ways. First, logs: redirect each task's output to its own log file, and log a timestamped line at its start and end, so you open the log and see the last run and when it happened. Second, the php artisan schedule:list command, which shows every scheduled task and its next due times, confirming that what is in the code is what you expect. Third, the lock itself: watch that the withoutOverlapping lock is released after each run, because if it stays stuck because the task died suddenly, all following runs stop silently, which is why you set a lock expiry via withoutOverlapping(10) in minutes so it releases automatically if the job crashes. For critical tasks add heartbeat monitoring that pings an external service on each successful run, so if the signal stops you get an alert that the task halted.
Where This All Runs: The Server Environment
Everything above assumes full server access over SSH, which is exactly what you get on a cloud VPS where you hold root and edit crontab yourself. Shared hosting usually limits cron or blocks some commands, so if your project depends on precise scheduling and background jobs, the VPS is its natural home. CLOUD servers in Riyadh answer in about 8ms, and activation is automatic within seconds after payment, so you start configuring your schedule right away. Review the plans and pricing to pick the specs that fit your load.
The Practical Takeaway
Understand the five fields first, always use absolute paths, and redirect output to a log. In a Laravel application place just one cron line running schedule:run every minute, and manage all your tasks from code under Git instead of stuffing crontab. Protect your tasks from overlap with withoutOverlapping and from duplication across servers with onOneServer. Always verify through logs, schedule:list, and heartbeat monitoring for critical tasks, because scheduling you do not monitor is not scheduling, it is a deferred surprise.


