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 * 5

This 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/Berlin at the top of the file (as the tool's General Best Practices block recommends). In containers, set TZ=Europe/Berlin as 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>&1 after 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>&1

flock -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.