
September 11, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
When you need to master cron for scheduled jobs, you are really learning how Linux turns a clock into a reliable worker. Cron fires commands at fixed intervals without a human clicking anything. That sounds simple until a backup never runs, a Laravel invoice job fires twice, or a path breaks after deployment. On production Linux servers in Nepal and abroad, cron is still the default trigger for nightly dumps, cache clears, report emails, and framework schedulers. This guide walks through crontab syntax, Laravel integration, monitoring, and the mistakes I see on real client projects.
What is cron and how does it schedule jobs on Linux?
Cron is the time-based job scheduler built into Unix-like systems. The cron daemon reads crontab files and launches shell commands when the current minute matches each entry. Each line is independent. There is no central queue unless your application provides one.
On Ubuntu 22.04 and 24.04 servers I maintain, cron runs as a system service. User crontabs live per account. System-wide jobs sit in /etc/cron.d/, /etc/cron.daily/, and related directories. For application work, I almost always use the deploy user's crontab rather than root unless the task truly needs elevated privileges.
Where crontab entries live
User crontabs are edited with crontab -e and stored under /var/spool/cron/crontabs/. List them with crontab -l. System jobs in /etc/cron.d/ include a user column. That format matters when PHP-FPM runs as www-data but your app deploys as deploy.
For a quick orientation, see the companion piece on how cron jobs schedule tasks on Linux. It covers the same fundamentals with fewer framework specifics.
How do you write a correct crontab schedule expression?
A standard crontab line has five time fields plus a command. Order is minute, hour, day of month, month, day of week. Values range from 0–59 for minutes and 0–23 for hours. Day of week uses 0 or 7 for Sunday on most systems.
# m h dom mon dow command
0 2 * * * /usr/local/bin/backup.sh
*/15 * * * * /usr/bin/php /var/www/app/artisan queue:work --stop-when-empty
0 0 1 * * /usr/bin/find /var/log/app -name "*.log" -mtime +30 -delete The first example runs daily at 02:00. The second runs every fifteen minutes. The third runs at midnight on the first day of each month. Special strings like @daily and @reboot also work, though I prefer explicit numeric fields in production because they are easier to audit.
Crontab field reference
| Field | Allowed values | Example | Meaning |
|---|---|---|---|
| Minute | 0–59 | */5 | Every 5 minutes |
| Hour | 0–23 | 2-4 | Hours 2, 3, and 4 |
| Day of month | 1–31 | 1,15 | 1st and 15th |
| Month | 1–12 | * | Every month |
| Day of week | 0–7 | 1-5 | Monday through Friday |
Use a regex tester mindset when validating patterns. Cron is not regex, but the habit of testing before production saves hours. The official crontab manual page remains the authoritative reference for field semantics.
A common mistake is mixing day-of-month and day-of-week with unintended OR logic. If both are restricted, cron runs when either condition matches on many implementations. When you need AND logic, wrap logic inside a shell script instead.
How do you run Laravel scheduled tasks with cron in production?
Laravel does not rely on dozens of crontab lines. You register tasks in routes/console.php or app/Console/Kernel.php on older versions. One system cron entry calls php artisan schedule:run every minute. Laravel decides which defined tasks fire in that minute.
On Laravel 13.x with PHP 8.3 or higher, a typical production entry looks like this:
* * * * * cd /var/www/myapp/current && /usr/bin/php8.3 artisan schedule:run >> /var/log/myapp-scheduler.log 2>&1 Notice three details. The path uses cd so relative paths resolve correctly. The PHP binary is explicit because servers often carry multiple versions side by side. Output redirects to a log file because cron emails are easy to miss.
I use this pattern on booking platforms like Adventure Third Pole Trek where reminders, supplier sync, and report generation must run on time. The application code lives in version control. Cron only triggers the scheduler.
Define tasks in Laravel
use Illuminate\Support\Facades\Schedule;
Schedule::command('reports:daily')
->dailyAt('06:30')
->timezone('Asia/Kathmandu')
->withoutOverlapping()
->appendOutputTo(storage_path('logs/daily-report.log'));
Schedule::job(new SendPaymentReminders)
->hourly()
->onOneServer(); The withoutOverlapping() mutex prevents a slow run from stacking duplicates. The onOneServer() call requires a shared cache like Redis 8.10 when multiple app servers exist. For deeper Laravel scheduling setup, read Laravel scheduled tasks in production.
WordPress 7.1 and WooCommerce 11.1 use a different model. WP-Cron triggers on page visits unless disabled. On production sites I disable pseudo-cron and use system cron to call wp-cron.php instead. Magento 2.4.x relies on its own cron consumer groups. The principle stays the same: one reliable system trigger beats hope.
How do cron jobs differ from queue workers for background processing?
Cron answers the question "run this at 03:00." A queue worker answers "process this job when a worker is free." They overlap in Laravel because scheduled tasks can dispatch jobs. They are not interchangeable.
Use cron when timing matters. Examples include nightly backups, monthly billing, certificate renewal checks, and sitemap generation. Use queues when volume and retries matter. Examples include sending 2,000 emails, resizing uploads, and calling slow third-party APIs.
| Criteria | Cron / Scheduler | Queue Worker |
|---|---|---|
| Trigger | Wall-clock schedule | Job pushed to queue |
| Concurrency | One run per schedule slot | Many workers in parallel |
| Retries | Manual or app-level | Built-in with backoff |
| Long tasks | Risk overlap if slow | Designed for long work |
| Best for | Reports, cleanup, sync windows | Email bursts, imports, webhooks |
The dedicated comparison at Laravel cron vs queue worker walks through decision rules. For high-traffic apps, pair a single scheduler cron with persistent queue:work systemd units or Supervisor processes. Read scaling Laravel queues for high traffic for worker sizing guidance.
On a legal-tech portal I built, document expiry reminders run on a schedule. The scheduler dispatches individual notification jobs to the queue. Cron stays fast. Heavy work happens asynchronously. That split is the pattern I recommend most often.
How do you debug and monitor cron jobs that fail silently?
Cron failures are quiet by default. If a command produces no output and exits zero, you see nothing. If it fails before logging starts, you may still see nothing. Treat observability as part of the job definition, not an afterthought.
Verification checklist
- Confirm the crontab owner matches the deploy user with
crontab -l -u deploy. - Check syslog with
grep CRON /var/log/syslogon Ubuntu. - Read your redirect log file after the expected run time.
- Run the exact command manually from an SSH session as the same user.
- Verify environment variables; cron runs with a minimal PATH.
- Confirm Deployer symlink paths if you use zero-downtime releases.
I have lost hours to stale paths in cron after a Deployer 7 release swap. The fix is always the same: point cron at /var/www/app/current, not a dated release folder. Sister sites on my shared pipeline taught me to document the cron user in the deploy README.
# Good: stable symlink path
* * * * * cd /var/www/notary/current && /usr/bin/php artisan schedule:run >> storage/logs/cron.log 2>&1
# Bad: hard-coded release path that breaks on next deploy
* * * * * /usr/bin/php /var/www/notary/releases/47/artisan schedule:run For backup jobs, follow the rsync and cron guide at automate server backups with rsync and cron. Add a simple alert if the log file has not been updated in 26 hours. Uptime monitors and log watchers both work. The goal is to learn about failure before a client does.
Laravel provides schedule:monitor on supported versions to track expected run times. External uptime tools can HTTP ping a signed route that confirms last successful batch completion. Pick one layer of proof minimum per critical job.
What are common cron mistakes on production servers?
Most cron incidents I troubleshoot are environmental, not syntax errors. The schedule expression is correct. Everything else is wrong.
- Relative paths: Cron starts in the user's home directory. Always
cdto the app root first. - Missing PHP binary: Specify
/usr/bin/php8.3when multiple versions coexist. - Permission drift: Files owned by root after a manual fix block the deploy user.
- Duplicate schedulers: Two crontab lines on blue-green servers without
onOneServer()cause double billing. - Timezone blindness: Server UTC vs app
Asia/Kathmandushifts reports by five hours and fifteen minutes. - No overlap guard: A slow import run twice stacks until the database locks.
Nepal-based businesses often need Bikram Sambat dates in reports. Keep calendar conversion in application code. Use a Nepali date converter for validation during development. Do not ask cron to understand BS dates directly.
For WordPress stacks under WordPress development, disable WP-Cron in wp-config.php and hit wp cron event run --due-now from system cron. For custom PHP on Symfony 8.1, Symfony Scheduler can replace raw crontab entries with PHP-defined schedules similar to Laravel.
Security matters too. Cron runs with the privileges of its owner. Do not store secrets in crontab lines where crontab -l exposes them. Load secrets from .env inside the application. Restrict file permissions on log directories.
If cron supports infrastructure you do not want to own, consider ongoing support and maintenance. A missed backup cron line costs far more than Rs 5,000/month (~USD 37) in monitoring time.
Platform-specific notes for 2026 stacks
On Laravel 12 supported through February 2027, scheduling works the same way as Laravel 13.x. Plan PHP 8.3 before upgrading to Laravel 13. MySQL 9.7 backup jobs should use mysqldump with credentials from a secured option file, not inline passwords.
Redis-backed queues complement cron but do not replace it. Memcached 1.6.x caches session data; it does not schedule work. Node.js 26 LTS build scripts belong in CI, not cron, unless you truly need a nightly asset rebuild on the server.
For broader Ubuntu-specific examples, see the Ubuntu cron jobs guide and background jobs vs cron design choice. The Laravel scheduling documentation covers mutex, timezone, and maintenance mode behaviour.
Key Takeaways
- One well-formed crontab line with absolute paths and log redirection beats five undocumented entries.
- Laravel needs only
* * * * * schedule:run; define timing inside the app with overlap guards. - Use cron for clock-driven work and queues for volume-driven work; combine them when schedules dispatch jobs.
- After every Deployer release, confirm cron still points at the
currentsymlink, not an old release path. - Monitor log freshness or use
schedule:monitorso silent failures surface before customers notice. - Set explicit timezones in application schedulers when server UTC and business local time differ.
People Also Ask
How often should cron run for Laravel scheduled tasks?
Run php artisan schedule:run every minute. Laravel evaluates which registered tasks are due during that minute. Running less often skips sub-hourly schedules unless you redesign them.
Can two cron jobs run at the same time?
Yes. Cron starts independent processes. If that is unsafe for your task, use flock, Laravel's withoutOverlapping(), or a dedicated queue worker so only one instance processes the work.
Why does my cron job work in SSH but not in crontab?
SSH loads your shell profile and PATH. Cron does not. Use full binary paths, set cd to the project root, and export required variables inside the command or a wrapper script.
Should I use root crontab for web application tasks?
Usually no. Run jobs as the deploy or application user so file permissions stay consistent. Use root only for system tasks like certificate renewal or log rotation that truly require it.
Build reliable scheduled jobs on your stack
To master cron for scheduled jobs, treat the crontab line as production code. Review it during deploys, log every run, and test as the cron user before closing the ticket. Pair system cron with framework schedulers on Laravel, disable pseudo-cron on WordPress, and keep queue workers separate for heavy lifting. If your server still misses backups or sends duplicate invoices, the fix is usually path, user, or timezone — not the schedule syntax itself.
Need help hardening cron on a live app, Laravel scheduler, or backup pipeline? See custom software development and Notary Nepal for examples of scheduled workflows in production. Browse more guides on the blog, explore free developer tools, or contact us to audit your current cron setup.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

