# Crontab Errors: "bad minute", "premature EOF", and Other Messages

By Dan Ochoa · Published 2026-10-30 · Updated 2026-10-30
Canonical: https://cronradar.com/blog/crontab-errors

> What crontab's "bad minute", "bad command", "premature EOF", and "errors in crontab file, can't install" mean in cronie, Debian, and BusyBox, and each fix.

**`"/tmp/crontab.Xq3fLa/crontab":4: bad minute` followed by `errors in crontab file, can't install.` means `crontab` couldn't parse the first field of a line in the file you saved, so it refused the whole file and left your installed crontab unchanged.** Debian and Ubuntu print that second line; cronie, the cron on Fedora, RHEL, and Arch, prints `Invalid crontab file, can't install.` instead. BusyBox's `crontab`, on Alpine, installs anything, and its errors appear later in crond's log as `user root: parse error at ...`.

Below, each message is traced to the source line that prints it, with what triggers it and the fix. This guide covers cronie 1.7.2, the current release on [GitHub](https://github.com/cronie-crond/cronie/releases/tag/cronie-1.7.2); Debian's cron 3.0pl1-197, the version in Debian 13 ([tracker.debian.org](https://tracker.debian.org/pkg/cron)); and BusyBox 1.37.0, the version in Alpine 3.24 ([APKBUILD](https://gitlab.alpinelinux.org/alpine/aports/-/blob/3.24-stable/main/busybox/APKBUILD)) (checked October 30, 2026). Debian testing's [cron 3.0pl1-212](https://salsa.debian.org/debian/cron/-/tree/debian/3.0pl1-212) prints the same messages plus a `pathname too long` check on the file argument ([patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-212/debian/patches/fixes/crontab-raises-an-error-for-too-long-CLI.patch#L24-29)), and BusyBox 1.38.0's [crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_38_0/miscutils/crond.c) prints the same messages as 1.37.0's. Ubuntu 24.04's cron 3.0pl1-184ubuntu2 prints all of Debian's except `unsafe mail` ([Launchpad](https://git.launchpad.net/ubuntu/+source/cron/tree/crontab.c?h=applied/ubuntu/noble)). RHEL 9's cronie 1.5.7 carries Red Hat backports of the step-size warning, the `-n` job option, and the 10,000-entry limit ([CentOS Stream 9 cronie.spec](https://gitlab.com/redhat/centos-stream/rpms/cronie/-/blob/e7a03758c1ec4958210449dec7d1d2916948ad26/cronie.spec#L16-47)), so it prints the same messages as cronie 1.7.2. Editing crontabs and checking one without installing it are covered in the [crontab guide](https://cronradar.com/blog/crontab-guide); to find out which cron you run, see [cron implementations](https://cronradar.com/blog/cron-implementations).

## Which Cron Printed It

| Message                                                          | Printed by                  | When                                                    |
| ---------------------------------------------------------------- | --------------------------- | ------------------------------------------------------- |
| `"FILE":N: bad minute`, `bad hour`, ... `bad command`            | cronie and Debian `crontab` | A line fails to parse                                   |
| `Invalid crontab file, can't install.`                           | cronie `crontab`            | After any parse error                                   |
| `errors in crontab file, can't install.`                         | Debian and Ubuntu `crontab` | After any parse error                                   |
| `"FILE":N: premature EOF`                                        | cronie `crontab`            | The last line has no newline                            |
| `new crontab file is missing newline before EOF, can't install.` | Debian and Ubuntu `crontab` | The last line has no newline                            |
| `Do you want to retry the same edit? (Y/N)`                      | cronie `crontab -e`         | After a refused edit                                    |
| `Do you want to retry the same edit? (y/n)`                      | Debian `crontab -e`         | After a refused edit                                    |
| `(CRON) bad minute (/etc/cron.d/app)`                            | cronie `crond`              | Loading a file with a bad line, which it skips          |
| `Error: bad minute; while reading /etc/cron.d/app`               | Debian `cron`               | Loading a file with a bad line, then ignoring that file |
| `user root: parse error at 60`                                   | BusyBox `crond`             | Loading a crontab with a field it can't read            |

Sources: cronie [entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L51-L63), [crontab.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L874-L897), and [user.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/user.c#L119-L164); Debian [entry.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/entry.c#L42-52), [crontab.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L555), and the patches cited below; BusyBox [crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L321-L324).

## How crontab Reports a Bad Line

cronie and Debian check the file when you save from `crontab -e`, run `crontab file`, or pipe into `crontab -`. On the first line that fails, both print the file name, a line number, and the reason with the same format string, `"%s":%d: %s` ([cronie crontab.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L611-L614), [Debian crontab.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L289)), then the summary line:

```text
"/tmp/crontab.Xq3fLa":4: bad minute
Invalid crontab file, can't install.
```

- **The file name** is crontab's temporary copy when you used `crontab -e` (by default `/tmp/crontab.XXXXXX` on cronie and `/tmp/crontab.XXXXXX/crontab` on Debian), the path you passed, or `-` for standard input ([cronie](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L683-L684), [L366-L369](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L366-L369); [Debian](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Allow-editors-with-tmpfiles.patch#L51-84)).
- **Only the first error is reported.** Both stop checking at the first bad line ([cronie](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L1074), [Debian](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L534-539)). Fix it and check again to find the next one.
- **Debian's line number is usually one too low.** crontab prints its line count minus one ([crontab.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L289)), which is right only once the bad line's newline has been read. Most errors, such as an out-of-range value, a bad name, or a `*` where the command should start, are found earlier in the line and printed before crontab reads its newline ([entry.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/entry.c#L296-301)), so they're reported against the line above. [Bug #571176](https://bugs.debian.org/571176), still open, shows both cases: `PENG AM` on line 2 reported as line 1, and a lone `BLA` on line 4 reported correctly. Check the line after the one named.
- **Nothing is installed.** `crontab file` exits with status 1 ([cronie](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L202-L205), [Debian](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L124-125)), and `crontab -e` asks whether to retry, covered [below](#do-you-want-to-retry-the-same-edit).

## bad minute, bad hour, bad day-of-month, bad month, bad day-of-week

These five name the field crontab was parsing when it gave up ([cronie entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L228-L276)). Both implementations accept the same values in each field ([cronie macros.h](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/macros.h#L112-L131), [Debian cron.h](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/cron.h#L122-140)); [cron expression syntax](https://cronradar.com/blog/cron-expression#the-five-fields) lists them and the operators. What triggers the messages in practice:

- **A value out of range**, such as `60` for a minute, `24` for an hour, `0` or `32` for a day of month, `13` for a month, or `8` for a day of week.
- **A full name.** Names are three letters in any case, so `Jan` and `mon` work and `January` and `Monday` don't ([cronie entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L723-L733), [Debian cron.h](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/cron.h#L244-252)).
- **Quartz or Spring syntax.** `?`, `L`, `W`, `5L`, and `MON#1` aren't cron syntax; [cron last day of month](https://cronradar.com/blog/cron-last-day-of-month) shows how to get the effect of `L`. Debian reads every letter and digit into the value, so `L`, `W`, and `5L` fail there too ([entry.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/entry.c#L449-489)). After a valid value, though, it skips any other characters up to the next blank ([L344](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/entry.c#L344)), so `0 9 * * MON#1` installs there as every Monday, while cronie rejects it.
- **A seconds field.** Spring and Quartz expressions begin with seconds, so every value lands one field early: `0 30 2 * * *`, meant as 02:30:00, puts `30` in the hour field and fails with `bad hour`.
- **A step without a range.** `5/15` is `bad minute` in both ([cronie entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L612-L626), [Debian patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/crontab-entry-parsing-fixes.patch#L43-47)); write `5-59/15`. BusyBox accepts `5/15` without a message and runs only at minute 5 ([crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L283-L311)).
- **A step of zero**, as in `*/0` ([cronie](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L596-L603), [Debian patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/crontab-entry-parsing-fixes.patch#L57)).
- **A line that isn't a valid variable.** Any line that isn't blank, a comment, or `NAME=value` is parsed as a job, so its first word is read as the minute field ([cronie env.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/env.c#L182-L276), [Debian patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Backport-crontab-envparser-from-v4.1.patch#L100-125)). `export PATH=/usr/local/bin:/usr/bin:/bin`, `source ~/.profile`, and `MAILTO ops@example.com` without its `=` all fail as `bad minute`. Write `PATH=/usr/local/bin:/usr/bin:/bin`; [cron environment variables](https://cronradar.com/blog/cron-environment-variables) covers what a variable line can and can't do.

### The Step-Size Warning Is Not an Error

```text
Warning: Step size 90 higher than possible maximum of 59
```

cronie and Debian print this for a step larger than the span of the range it steps through, which for `*` is the whole field, then install the line anyway ([cronie entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L680-L684), [Debian patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Step-size_out_of_bound_handling.patch#L89)). `*/90 * * * *` then runs at minute 0 of every hour, because the next step lands past 59. A 90-minute interval takes two lines:

```text
0 0-21/3 * * * /opt/app/sync.sh
30 1-22/3 * * * /opt/app/sync.sh
```

The first runs at 00:00, 03:00, and so on through 21:00; the second at 01:30, 04:30, and so on through 22:30.

## bad command

crontab doesn't check that the command exists. cronie prints `bad command` when nothing follows the time fields, or when the command would start with `*` ([entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L284-L289)); Debian's checks only for the `*` ([Better-timeskip-handling.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/features/Better-timeskip-handling.patch#L489-491)). cronie's source says only that the `*` check catches "a common typo". Among the lines it rejects is a six-field expression whose sixth field is `*`, like this Spring one:

```text
# "Every 5 minutes" in Spring's six-field syntax: rejected with bad command
0 */5 * * * * /opt/app/poll.sh

# The five-field cron equivalent
*/5 * * * * /opt/app/poll.sh
```

Only a `*` is caught. Any other sixth field becomes the start of the command, so the Quartz expression `0 0 2 * * ? /opt/app/report.sh` installs without an error and runs the command `? /opt/app/report.sh` at 00:00 on the 2nd of every month. Cron's smallest interval is a minute; [cron every 30 seconds](https://cronradar.com/blog/cron-every-30-seconds) covers the workarounds.

## Other Messages That Refuse a Crontab

| Message                                                        | Printed by     | Cause and fix                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bad time specifier`                                           | cronie, Debian | `@` followed by anything other than `reboot`, `yearly`, `annually`, `monthly`, `weekly`, `daily`, `midnight`, or `hourly`, in lowercase; `@Daily` and `@every` fail ([cronie](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L156-L215), [Debian](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/entry.c#L110-157))                                                                                                                                     |
| `bad option`                                                   | cronie         | A `-` option before the command other than a single `-n`, or a line starting with `-` in a non-root user's crontab ([entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L138-L148), [L420-L448](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L420-L448))                                                                                                                                                                                  |
| `unsafe mail`                                                  | Debian 13      | A `MAILTO` or `MAILFROM` value that doesn't start with a letter or digit, or contains a character other than letters, digits, and `@!:%-.,_+=/`. `MAILTO=a@example.com, b@example.com` fails on the space; remove it ([check_e_mails.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/features/check_e_mails.patch#L35-48), [safe_p](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/features/check_e_mails.patch#L113-119)) |
| `command too long`                                             | Debian         | A command of 999 characters or more; move it into a script ([Dont-silently-truncate-commands.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Dont-silently-truncate-commands.patch#L47-67))                                                                                                                                                                                                                                                        |
| `crontab is too long; maximum number of lines is 10000.`       | Debian         | More than 10,000 lines ([Enforce-maximum-crontab-line-count.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Enforce-maximum-crontab-line-count.patch#L86))                                                                                                                                                                                                                                                                                         |
| `There are too many entries in the crontab file. Limit: 10000` | cronie         | More than 10,000 entries; a matching message caps variables at 1,000 ([crontab.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L1107-L1119))                                                                                                                                                                                                                                                                                                                       |

## premature EOF and the Other Missing-Newline Messages

A crontab whose last line has no newline is refused by both `crontab` commands; the [crontab guide](https://cronradar.com/blog/crontab-guide#comments-blank-lines-and-the-final-newline) explains why and how to test a file for it. The message depends on which program reads the file:

| Reader           | Message                                                                               | Result                         |
| ---------------- | ------------------------------------------------------------------------------------- | ------------------------------ |
| cronie `crontab` | `"FILE":N: premature EOF`, then `Invalid crontab file, can't install.`                | Refused; `N` is the last line  |
| Debian `crontab` | `new crontab file is missing newline before EOF, can't install.`                      | Refused                        |
| cronie `crond`   | `(CRON) missing newline before EOF (/etc/cron.d/app)`                                 | The last line is skipped       |
| Debian `cron`    | `(*system*app) ERROR (Missing newline before EOF, this crontab file will be ignored)` | The whole file is ignored      |
| BusyBox          | None                                                                                  | The last line is read normally |

Sources: cronie [crontab.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L1084-L1090) and [user.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/user.c#L127-L136); Debian [crontab-detect-missing-eol.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/crontab-detect-missing-eol.patch#L41-46) and [Abort-processing-of-crontabs-with-errors.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Abort-processing-of-crontabs-with-errors.patch#L29-32).

A comment as the last line triggers none of these, because both parsers read a comment through to the end of the file before looking for another entry ([cronie misc.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/misc.c#L416-L458), [Debian env.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/env.c#L123-124)). The crontab guide's [install script](https://cronradar.com/blog/crontab-guide#crontab-file-replaces-everything) adds the newline only when it's missing.

## Do You Want to Retry the Same Edit?

cronie and Debian ask this after `crontab -e` saves a file that fails the check, missing newline included; the [crontab guide](https://cronradar.com/blog/crontab-guide#syntax-is-checked-when-you-save) covers what `y` and `n` do, and that BusyBox never asks. cronie's prompt ends in `(Y/N)` and Debian's in `(y/n)`, both take either case, and any other answer prints `Enter Y or N` and asks again ([cronie crontab.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L874-L897), [Debian crontab.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L455-474), [Improve-retry-prompt.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Improve-retry-prompt.patch#L22)). After `n`, crontab prints `crontab: edits left in /tmp/crontab.Xq3fLa` (on Debian, a path ending in `/crontab`); fix that file and install it with `crontab /tmp/crontab.Xq3fLa`.

The installed crontab is untouched until a check passes. Two consequences for automation: `crontab -e` exits with status 0 even after **n** ([cronie](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/crontab.c#L188-L201), [Debian](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/crontab.c#L116-123)), and neither implementation stops asking when standard input is closed. cronie reprints the prompt when the read fails, and Debian answers its own empty read with `Enter Y or N`, so a script that drives `crontab -e` through `$EDITOR` loops forever on a syntax error. Scripts should install with `crontab file`, which exits 1 instead; the [crontab guide](https://cronradar.com/blog/crontab-guide#crontab-file-replaces-everything) has a script that adds an entry that way without dropping the others.

## When the Daemon Finds the Error

Nothing checks `/etc/crontab` or `/etc/cron.d` files before the daemon reads them, and the same goes for a user crontab copied into the spool directory instead of installed with `crontab`. The daemon logs the reason and the file, but not the line:

| Daemon       | Log messages                                                                                                                     | Effect                              |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| cronie crond | `(CRON) bad minute (/etc/cron.d/app)`                                                                                            | That line is skipped; the rest runs |
| Debian cron  | `Error: bad minute; while reading /etc/cron.d/app`, then `(*system*app) ERROR (Syntax error, this crontab file will be ignored)` | Every job in the file stops         |

Sources: cronie [user.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/user.c#L137-L150) and [misc.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/misc.c#L527-L529); Debian [Logging-enhancements.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/features/Logging-enhancements.patch#L80-86) and [Abort-processing-of-crontabs-with-errors.patch](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/patches/fixes/Abort-processing-of-crontabs-with-errors.patch#L44-46). For `/etc/crontab`, Debian logs `while reading /etc/crontab` and `(*system*)`; for a user's crontab, `while reading crontab for user alice` and `(alice)`.

The user field has its own failures, since system crontab lines carry one and user crontabs don't. Debian reads the first word after the time fields as the account and logs `bad username` when no such account exists, which is what a line missing its user field usually produces ([entry.c](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/entry.c#L224-240)). cronie loads such a line and, at each scheduled time, logs `getpwnam() failed - user unknown` instead of running it ([entry.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/entry.c#L294-L316), [job.c](https://github.com/cronie-crond/cronie/blob/cronie-1.7.2/src/job.c#L55-L77)); with a one-word command, the line is `bad command` instead.

Find the messages in the journal. Debian's unit is `cron` ([cron.service](https://salsa.debian.org/debian/cron/-/blob/debian/3.0pl1-197/debian/cron.service)) and Fedora installs cronie's as `crond` ([cronie.spec](https://src.fedoraproject.org/rpms/cronie/blob/rawhide/f/cronie.spec#_125)):

```bash
journalctl -u cron --since today | grep -E 'Error: |ERROR \('   # Debian, Ubuntu
journalctl -u crond --since today | grep -F '(CRON)'            # Fedora, RHEL
```

To find the line, check a copy of the file with crontab's own parser. This drops the user field from each job line, keeps every other line as it is so line numbers still match, and installs nothing:

```bash
# Run as root. Field 6 is the user on a time-field line; field 2 on an @ line.
awk '/^[[:space:]]*(#|$)/ || /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/ { print; next }
     /^[[:space:]]*@/ { $2 = ""; print; next }
     { $6 = ""; print }' /etc/cron.d/app > /tmp/app-check.cron
```

Then check the copy with your implementation's syntax-check option and delete it. The [crontab guide's options table](https://cronradar.com/blog/crontab-guide#the-crontab-options-at-a-glance) lists which versions have `-n` and `-T`, and what `-n` does on cronie instead, so run only your implementation's line:

```bash
crontab -n /tmp/app-check.cron && rm /tmp/app-check.cron   # Debian, Ubuntu
crontab -T /tmp/app-check.cron && rm /tmp/app-check.cron   # cronie
```

The copy can't reveal a missing final newline, because awk ends every line with one, or a missing user field, which Debian's log names as `bad username` and cronie's as `getpwnam() failed - user unknown`, or as `bad command` for a one-word command.

## BusyBox: No Check, One Log Message

BusyBox's `crontab` copies the file into place without parsing it ([crontab.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crontab.c#L176-L191)), so it never prints a syntax error. crond parses the file when it next loads it, and for a field it can't read it logs one line ([crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L321-L324)):

```text
user root: parse error at 60
```

`root` is the crontab's file name, which BusyBox uses as its user name ([L445](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L445)), and `60` is the field's text. The line stays loaded, and what the field then matches depends on where parsing stopped:

- **An out-of-range number fills the whole field.** The fill loop wraps around until a failsafe ends it ([crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L296-L311)), so `60` in the minute field runs every minute, and `7` in the day-of-week field runs every day, as [cron implementations](https://cronradar.com/blog/cron-implementations#busybox-crond-three-variables-and-one-directory) notes.
- **An unrecognized character leaves the field empty.** `L` or `?` stops parsing before anything is set ([crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L270-L273)). In the minute, hour, or month field, the line then never runs. In a day field, it never runs only if the other day field is `*`, because BusyBox runs a line when either day field matches: `0 9 ? * MON` still runs every Monday ([L336-L360](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L336-L360), [L912](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L912)).

Other mistakes produce no message at all:

- A line with fewer than six fields, such as one with no command, is skipped ([crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L546-L547)).
- An unknown `@` keyword is skipped ([L542](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L542)).
- A variable other than `MAILTO`, `SHELL`, or `PATH` is skipped because it has fewer than six fields ([L546-L547](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L546-L547)). One with six or more words, such as `MSG=a b c d e f`, is read as a job instead and logs `parse error`.
- A crontab not owned by root is ignored ([L440](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L440)). One named after an account that doesn't exist is logged only at log level 7 or lower, and the default is 8 ([L429-L431](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L429-L431), [L168](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L168), [L178](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L178)).

A non-root crontab is also cut off at `MAXLINES`, 256 by default, with `user alice: too many lines` ([L103-L104](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L103-L104), [L451-L453](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L451-L453)). crond logs to syslog unless started with `-L FILE`, or with `-d N`, which logs to standard error ([crond.c](https://github.com/vda-linux/busybox_mirror/blob/1_37_0/miscutils/crond.c#L1048-L1052)).

## Catch the Jobs a Bad Crontab Stops

A refused `crontab` keeps the old crontab, so a job you meant to add doesn't exist and a job you meant to change keeps its old schedule. On Debian and Ubuntu, one bad line in `/etc/crontab`, an `/etc/cron.d` file, or a crontab written outside `crontab` stops every job in that file, and the only trace is two log lines. Nothing on the host raises an alert.

A check-in monitor notices from outside. With CronRadar, each run reports `start`, then `complete` or `fail`; a `fail` opens an incident immediately, and a run that hasn't reported by its scheduled time turns its monitor `warning`, then `critical` once the grace period runs out. Alerts go by email, Slack, webhook, Discord, or PagerDuty. There's no CronRadar SDK or auto-discovery for crontab, so each job reports with its own HTTP check-in. [Cron grace period](https://cronradar.com/blog/cron-grace-period) has a wrapper script that reads the API key from a file only the job's account can read, sends it with HTTP Basic auth, and exits with the job's own status, so the key never appears in the crontab. CronRadar reads every schedule as five fields in UTC, so convert the crontab line's times to UTC, write `@` shortcuts out as fields, and use `0`, not `7`, for Sunday. An entry that restricts both day fields needs two entries, each with its own job key, because CronRadar expects a run only on dates that match both fields, where cron runs on either ([schedules](https://docs.cronradar.com/schedules)).

A monitor created by the job's first check-in doesn't exist until that check-in arrives, so a line that never installed has nothing to go `critical`. Register each job before its first run with [`POST /sync`](https://docs.cronradar.com/api-reference#framework-registry-sync) in `upsert` mode instead: a monitor that never receives a check-in then turns `critical`, with a `NeverExecuted` alert, once its first expected time plus the grace period has passed.

## Frequently Asked Questions

### What does "errors in crontab file, can't install" mean?

Debian's or Ubuntu's `crontab` found a line it couldn't parse, printed it above this message as `"FILE":LINE: reason`, and kept your current crontab. Fix that line and install again; only the first bad line is reported per check. cronie prints `Invalid crontab file, can't install.` for the same thing.

### What does "bad minute" mean in crontab?

crontab couldn't read the first field of a line. Common causes are a value outside 0-59, a step without a range such as `5/15`, and a line that is neither a job nor a valid `NAME=value` variable, such as `export PATH=...`, whose first word is read as the minute.

### Why does crontab say "premature EOF"?

cronie's `crontab` prints it when the file's last line has no newline. Add one with `echo >> file` and install again. Debian's `crontab` reports the same problem as `new crontab file is missing newline before EOF, can't install.`

### How do I answer "Do you want to retry the same edit?"

Answer `y` to reopen the editor with your changes and fix the line named above the prompt. Answer `n` to quit; crontab keeps your edits in the temporary file it names, and the installed crontab is unchanged.

### Why doesn't crontab on Alpine report errors?

BusyBox's `crontab` installs files without parsing them. crond reports a bad field later in its log, as `user root: parse error at 60`, and silently skips lines with no command, unknown `@` keywords, and variable lines of fewer than six words other than `MAILTO`, `SHELL`, and `PATH`.
