Cron Job Helper — Tips & Tricks
Cron pitfalls: day-of-month and day-of-week as OR logic, the server time zone, suppressing output, MAILTO, absolute paths and locking against overlap.
Back to the overview: Cron Job Helper · Open the live tool: www.jpkc.com/tools/cron/
The Manual explains every tab; the Examples show concrete expressions. This page is about what tends to go wrong when writing cron — the traps you only notice on the third missed run.
The infamous OR logic between day-of-month and day-of-week
The single most important cron trap: when you restrict both the Day of Month field and the Day of Week field (neither is *), classic Unix cron combines them with OR, not AND. The job then runs on every day that satisfies either condition.
Example:
0 0 13 * 5This does not run "only on a Friday the 13th" but on every 13th of the month AND additionally every Friday. If you want "Friday the 13th", standard cron cannot express it in a single line.
Practical takeaway: always restrict only one of the two day fields and leave the other on *. The builder's plain-text preview phrases such cases as "on day-of-month X and weekday Y" — but for the actual meaning, trust the real cron OR rule, because that is how the daemon behaves on the server.
Cron runs in the server's time zone — not yours
Cron uses the server's time zone (often UTC), not your local one. A 0 3 * * * is "3 AM server time". Two things follow:
- On the system you can set the crontab's time zone with
CRON_TZ=Europe/Berlinat the top of the file (as the tool's General Best Practices block recommends). In containers, setTZ=Europe/Berlinas an environment variable instead — otherwise the container runs in UTC. - The Next Scheduled Runs in the builder compute in your browser's time zone. That preview is a convenient estimate, but not necessarily identical to what the server does. Plan in server time when the hour matters.
Suppressing output: MAILTO vs. /dev/null
By default, cron emails any output (stdout and stderr) of a job to the local system user. A chatty script floods the inbox. There are two levers, which the Reference tab clearly separates:
MAILTO=""at the top of the crontab disables emails globally for the whole file.> /dev/null 2>&1after the command suppresses output per job — and also prevents a process from blocking on a full mail queue.
For web cron jobs, combining both is the most robust. Note: > /dev/null 2>&1 also redirects stderr (2>) — > /dev/null alone would still email error messages.
Use absolute paths
Cron starts with a minimal environment: $PATH is usually just /usr/bin:/bin, and your interactive profile (.bashrc, nvm, rbenv …) is not loaded. A command that works in your shell can fail silently under cron because the binary is not found. So: use absolute paths to the interpreter and script (/usr/bin/php /var/www/html/artisan …) instead of just php …. If a script needs particular environment variables, set them explicitly at the top of the crontab.
Specify the user (in system-wide crontabs)
In /etc/crontab and files under /etc/cron.d/, the username comes before the command — e.g. www-data. This is a common trap: the personal crontab (crontab -e) has no such field (the job runs as that user), while the system-wide files require it. The tool's CMS recipes show the system-wide form with www-data.
Prevent overlapping runs (flock)
If a job takes longer than its interval (e.g. a */5 import that sometimes runs 7 minutes), cron starts the next run anyway — two instances race and can clash. Guard against it with flock:
*/5 * * * * flock -n /tmp/mycron.lock php /var/www/html/artisan schedule:run >> /dev/null 2>&1flock -n only starts the job if the lock file is free, and skips it otherwise. The tool mentions this trick in its General Best Practices block.
Web cron vs. system cron — when to use which
The Web Cron tab builds jobs that call a URL via curl/wget — the schedule still runs in the system cron, only the command is an HTTP request. When is that worth it?
- Web cron is the right choice on shared hosting, where you lack shell access or control over CLI tools, and for CMS endpoints triggered over HTTP anyway (
wp-cron.php, Drupal's/cron/KEY). - System cron with CLI (WP-CLI, Drush,
artisan,console) is faster, more robust and bypasses the web server timeout. Where you have shell access it is the better choice — the CMS & Frameworks tab recommends the CLI path throughout.
When you protect a web cron endpoint, remember the Web Cron Tips from the tool: always use -s (silent) with curl, redirect output to a log file, secure the URL with a token/IP restriction/basic auth, set a timeout, and use HTTPS exclusively so credentials are not sent in plain text.
Quartz characters do not work in Unix cron
The Reference table lists ?, L, W and # and marks them explicitly as Quartz. These belong to the Java Quartz scheduler (and a few cron derivatives), not to plain Unix cron. A 0 0 L * * ("last day of the month") or 5#3 ("3rd Friday") is not understood by vixie/cronie cron. Use them only if your target scheduler speaks Quartz syntax; for a classic crontab, stick to * , - /.
Containers: log to stdout
In Docker/Podman, write cron output to /proc/1/fd/1 (or stdout) so it appears in docker logs — otherwise it vanishes. And mind the time zone: without TZ, the container runs in UTC. Both are in the tool's Container Cron Tips.
Combine with other JPKCom tools
- Docker tools — when you turn the container patterns from the Docker / DDEV tab into a real setup.
- Time & timestamp tools — to clarify the server time zone and Unix timestamps before committing to a clock time.
More context: the overview for the big picture, the Manual for every tab, and the Examples for concrete expressions. Try it all directly in the tool.