
August 25, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
A misconfigured cron job in Ubuntu fails quietly more often than it fails loudly. Backups stop. Invoices never send. Queue workers stall. On production VPS boxes I maintain for Laravel applications in Nepal, the scheduler is usually the first place I look when something worked yesterday but not today. This guide covers syntax, listing jobs, reading cron logs, debugging silent failures, and wiring Laravel Scheduler on Ubuntu 22.04 and 24.04 LTS.
Ubuntu ships the Vixie cron implementation through the cron package. The daemon reads crontab files, wakes at the right minute, and runs commands under a stripped-down shell environment. That minimal environment is why your interactive terminal test passes while the cron job in Ubuntu fails on the server. Treat every scheduled task like deployable code: version it, test it, and monitor it.
How do you set up a cron job in Ubuntu step by step?
Setting up a cron job in Ubuntu starts with choosing the right crontab owner. Application tasks should run as the app user — www-data, deploy, or a dedicated service account — not as root. Root cron belongs to system maintenance: log rotation, certificate renewal, and package cleanup.
Install and verify the cron service
On a fresh Ubuntu 24.04 server, cron is usually pre-installed. Confirm the service is active before adding entries:
sudo systemctl status cron
sudo systemctl enable cron
sudo systemctl start cron If status shows inactive, no schedule will fire regardless of how perfect your crontab looks. I have traced multi-day outages to a stopped cron unit after a failed package upgrade on a client VPS costing Rs 2,500/month (~USD 19).
Edit the correct crontab
Use crontab -e for the current user's jobs. Use sudo crontab -u www-data -e when the web application owner differs from your SSH login. System-wide entries live in /etc/crontab, /etc/cron.d/, and the /etc/cron.{hourly,daily,weekly,monthly} directories.
- Open the crontab:
crontab -e - Declare environment variables at the top (PATH, SHELL, TZ)
- Add one schedule line with absolute binary paths
- Redirect stdout and stderr to a dedicated log file
- Save, then confirm with
crontab -l - Watch syslog within the next minute if you used a test schedule
For Nepal-hosted servers, set timezone explicitly. Do not assume the server clock matches Kathmandu business hours:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TZ=Asia/Kathmandu
MAILTO=""
# Daily database backup at 2:30 AM NPT (Nepal Time)
30 2 * * * /usr/bin/mysqldump -u backup_user -p'SECRET' myapp >> /var/backups/myapp.sql 2>> /var/log/myapp-cron.log Empty MAILTO stops cron from trying to email root on every run. Email delivery rarely works on lean VPS setups anyway. Log files are the reliable audit trail.
What is the correct cron job syntax in Ubuntu?
Cron syntax on Ubuntu follows the standard five-field format documented in the crontab(5) man page. Minute, hour, day-of-month, month, and day-of-week define when the command runs. The command itself must use absolute paths because cron's default PATH is often just /usr/bin:/bin.
The five-field schedule
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of week (0 - 7, Sun = 0 or 7)
# │ │ │ │ │
# * * * * * /absolute/path/to/command
# Every day at 3:30 AM server local time
30 3 * * * /usr/bin/php /var/www/app/artisan schedule:run >> /var/log/app-cron.log 2>&1
# Every Monday at 9:00 AM
0 9 * * 1 /usr/local/bin/backup-script.sh
# WRONG: runs on 1st/15th OR Mondays — not both
0 9 1,15 * 1 /some/command When both day-of-month and day-of-week are restricted, standard Vixie cron treats them as OR logic. Developers expecting AND logic get surprised jobs firing on unintended days. Test ambiguous schedules with a regex tester mindset — map each field explicitly before deploying.
Special strings and shortcuts
Some crontab implementations accept shortcuts like @daily, @hourly, and @reboot. Ubuntu's cron supports these, but I avoid them in production crontabs. Explicit five-field syntax is clearer during audits and survives team handoffs better.
Percent sign escaping
Cron treats unescaped % as a newline character. Always escape percent signs in date format strings:
# Correct — escaped percent signs
0 3 * * * echo "$(date '+\%Y-\%m-\%d \%H:\%M:\%S') sync start" >> /var/log/sync.log 2>&1 This single gotcha causes more head-scratching than almost any other cron syntax issue. I have seen it break nightly report jobs on legal-tech portals where the date stamp never appeared in logs.
How do you list cron jobs in Ubuntu?
Listing cron jobs in Ubuntu requires checking every location cron reads from. A job missing from crontab -l may still exist in a system directory or another user's crontab. The query "list cron jobs ubuntu" usually means someone scheduled a task and cannot find it — or wants to audit what is actually running.
List user crontabs
# Current user's jobs
crontab -l
# Another user's jobs (requires sudo)
sudo crontab -u www-data -l
sudo crontab -u root -l
# List all user crontabs on the system
sudo ls -la /var/spool/cron/crontabs/ List system-wide cron entries
# Main system crontab (includes user column)
cat /etc/crontab
# Drop-in configs — common on packages like certbot
ls -la /etc/cron.d/
cat /etc/cron.d/*
# Periodic script directories
ls /etc/cron.hourly/
ls /etc/cron.daily/
ls /etc/cron.weekly/
ls /etc/cron.monthly/ Package managers often install cron snippets in /etc/cron.d/ during apt installs. Certbot renewal, logwatch, and unattended-upgrades all land there. When auditing a server during a Linux administration engagement, I grep every location before declaring the schedule clean.
# One-command audit across common locations
grep -rH . /etc/cron.d/ /etc/crontab /var/spool/cron/crontabs/ 2>/dev/null Why is my cron job in Ubuntu not running or failing silently?
Silent failure is cron's default behaviour. If a command exits with a non-zero status and produces no output, you hear nothing unless you configured logging or MAILTO. Debugging a cron job in Ubuntu means checking four areas: service status, syslog evidence, permissions, and environment mismatch.
Check cron logs in Ubuntu first
Ubuntu writes cron activity to syslog by default. The search query "cron logs ubuntu" maps directly to these commands:
# Recent cron executions
grep CRON /var/log/syslog | tail -50
# Filter by specific user
grep "CRON\[www-data\]" /var/log/syslog | tail -30
# Errors and permission denials
grep -iE "cron|denied|error" /var/log/syslog | tail -100 To isolate cron into its own file, uncomment the cron line in /etc/rsyslog.d/50-default.conf and restart rsyslog. Then read /var/log/cron.log directly. The Ubuntu Cron community guide documents this rsyslog split if you need persistent separation.
When syslog shows the command ran but the application did nothing, the bug lives inside the script — not the scheduler. Redirect all output during initial deployment:
0 3 * * * /usr/bin/php /var/www/app/artisan sync:orders >> /var/log/sync-orders.log 2>&1 Permission and ownership problems
Cron runs as the crontab owner. A job owned by root that writes to /var/www/app/storage owned by www-data creates intermittent permission errors. Match the cron user to the application owner. Scripts invoked directly need the executable bit, or call them through an interpreter:
# Preferred — explicit interpreter, no chmod dependency
0 4 * * * /bin/bash /var/www/app/scripts/nightly-cleanup.sh >> /var/log/cleanup.log 2>&1 Test without waiting for the schedule
- Simulate cron's environment:
env -i /bin/bash --noprofile --norc -c '/usr/bin/php /var/www/app/artisan inspire' - Validate run-parts scripts:
run-parts --test /etc/cron.daily - Minute-level smoke test: set schedule to
* * * * *, confirm two successful log entries, then revert immediately
Never leave a per-minute production job running. On a busy Laravel app, that mistake can hammer the database within an hour.
How do you run Laravel Scheduler with a cron job in Ubuntu?
Laravel applications should not scatter business schedules across system crontab. One cron job in Ubuntu calls schedule:run every minute. Laravel decides which tasks fire based on definitions in application code. I use this pattern on every Laravel project — from Nepal Gift Card to booking systems like Adventure Third Pole Trek.
The single crontab entry
* * * * * cd /var/www/app/current && /usr/bin/php artisan schedule:run >> /var/log/laravel-scheduler.log 2>&1 The cd matters. Artisan assumes project-root relative paths. On Deployer 7 deployments, point cron at the current symlink so zero-downtime releases do not require crontab edits. See my guide on Deployer rollback and release paths for the full symlink workflow.
Define tasks in Laravel 12
Laravel 12 (PHP 8.3+) defines schedules in routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('reports:daily')->dailyAt('06:00');
Schedule::command('sync:inventory')->everyFiveMinutes()->withoutOverlapping();
Schedule::command('billing:charge')->monthly()->onOneServer(); withoutOverlapping() prevents a slow sync from spawning duplicate workers. It requires a cache driver that supports atomic locks — Redis 8.10 is my default on production VPS setups. Read the comparison in my article on cron versus queue workers in Laravel when tasks exceed one-minute intervals or need retry logic.
For deeper Laravel scheduling patterns — overlap guards, onOneServer(), and monitoring — see the dedicated Laravel scheduled tasks production setup guide and the framework docs at laravel.com/docs/12.x/scheduling.
PHP version alignment
Ensure the PHP CLI binary matches your PHP-FPM version. On Ubuntu with PHP 8.3 or 8.4 side by side, verify with readlink -f $(which php) and hardcode that path. Mismatched versions cause scheduled tasks to miss extensions that web requests use. My Ubuntu server setup for PHP apps covers multi-version PHP management on the same box.
What are production security and maintenance practices for Ubuntu cron?
Reliable cron on production servers is an ops discipline, not a one-time config edit. Every scheduled task needs least-privilege execution, log rotation, external health checks, and idempotent design.
Least privilege and secrets
Never run application cron as root. Reserve root crontab for system tasks like rsync backup automation and certificate renewal. Keep secrets out of crontab when possible — Laravel loads .env automatically through Artisan. Plaintext passwords in crontab show up in process lists and backup dumps.
Monitoring beyond MAILTO
- Dead man's switch: ping Healthchecks.io or Cronitor on success; alert when pings stop
- Log shipping: forward cron logs to Loki or Elasticsearch via Promtail or Filebeat
- Application heartbeat: write a Redis timestamp after each run; a separate monitor checks freshness
On budget-conscious Nepal client projects, the Redis heartbeat pattern costs nothing beyond existing infrastructure. A stale key triggers a Slack webhook within minutes.
Log rotation prevents disk exhaustion
Cron jobs that append to logs without rotation will fill the disk. That causes cascading failures — MySQL crashes, PHP sessions break, the site goes down. Configure logrotate for every cron log:
# /etc/logrotate.d/myapp-cron
/var/log/myapp-cron.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data www-data
} Test with logrotate -d /etc/logrotate.d/myapp-cron. Pair this with broader Ubuntu server backup strategies so log and data retention policies align.
| Practice | Risk If Ignored | Effort |
|---|---|---|
| Absolute paths in every cron job in Ubuntu | Silent failure after package upgrade changes PATH | Low |
| Explicit TZ=Asia/Kathmandu on Nepal servers | Tasks fire at wrong local business hours | Low |
| Output redirected to rotated logs | No failure visibility; disk fills up | Low |
| Non-root application user | Privilege escalation if script is compromised | Medium |
| External health monitoring | Outages undetected for days | Medium |
| Idempotent task design | Duplicate charges or double emails on retry | High |
| Laravel Scheduler over raw cron sprawl | Config drift between servers and environments | Low |
How does a cron job in Ubuntu compare to systemd timers?
Cron remains the default for simple recurring tasks on Ubuntu. Systemd timers offer dependency chains, randomized boot delays, and journald integration. Choosing the wrong tool creates maintenance overhead without improving reliability.
Systemd timers excel when a task must wait for network availability or run relative to the last completion time. They need separate .service and .timer unit files per task. That overhead is justified for complex boot-time sequences. For everything else — nightly backups, Laravel scheduling, log cleanup — a cron job in Ubuntu is simpler and universally understood. Read my systemd services guide when timers genuinely fit better than cron.
On hardened production VPS boxes, combine cron with the practices in my Ubuntu security hardening guide and restrict who can edit crontabs through sudo policies. For full server provisioning context, see Laravel deployment on Ubuntu with Nginx and the broader DevOps automation patterns for Nepal teams.
Key Takeaways
- Every cron job in Ubuntu needs absolute binary paths, explicit PATH, and TZ set for Nepal servers using Asia/Kathmandu.
- List cron jobs in Ubuntu by checking user crontabs,
/etc/cron.d/, and/etc/crontab— not justcrontab -l. - Read cron logs in Ubuntu via
grep CRON /var/log/syslogbefore blaming the application code. - Redirect stdout and stderr to rotated log files; never rely on cron email on headless VPS setups.
- Laravel apps need one minute-level cron entry calling
schedule:run; define all tasks inroutes/console.php. - Test with
env -ito simulate cron's minimal shell before waiting for the scheduled minute.
People Also Ask
Where is the crontab file stored in Ubuntu?
User crontabs live in /var/spool/cron/crontabs/ as files named after each user. System-wide schedules sit in /etc/crontab and /etc/cron.d/. Edit them with crontab -e, not by opening spool files directly — direct edits get overwritten.
How often does cron check for jobs in Ubuntu?
The cron daemon wakes once per minute and evaluates all crontab entries against the current time. Sub-minute scheduling is not native to cron. Use a loop, systemd timer, or application-level scheduler if you need finer granularity.
Can I run a cron job as a different user in Ubuntu?
Yes. Root can assign jobs to any user with sudo crontab -u username -e. The /etc/crontab file includes a user field in its six-column format. Always match the cron user to the application file owner.
What is the difference between cron and crontab in Ubuntu?
Cron is the background daemon that executes scheduled commands. Crontab is both the file format and the command-line tool for editing a user's schedule. You write entries with crontab -e; cron reads and runs them.
Make Every Cron Job in Ubuntu Production-Ready
A reliable cron job in Ubuntu comes down to six habits: absolute paths, explicit environment, timezone awareness for NPT servers, log redirection, systematic listing and log review, and testing before you trust the schedule. For Laravel, delegate business timing to the framework and keep one system entry. These patterns have held up across fifteen years of production work on Ubuntu VPS infrastructure in Nepal and abroad.
If silent cron failures are costing you backups, billing runs, or queue processing, contact me for a scheduler audit. You can also send a message through my contact page with your crontab and syslog snippet. Explore full-stack development and server support services for end-to-end application and infrastructure work, or review the essential Ubuntu terminal commands reference for day-to-day server operations.
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.

