The Percent Trap
Scope: the single most surprising rule in the
crontab format — in the command field, % does not
mean what you think.
The rule
In a crontab line, the first unescaped % in the
command field ends the command. Everything after it
is fed to the command as standard input, and any
further % characters become newlines in that input.
Nothing else in Unix works like this, which is why it
ambushes everyone exactly once — usually inside a
date +format, the place % is most natural.
The ambush, staged
Two lab jobs, identical except for escaping, both appending to files so the difference can't hide:
* * * * * /bin/date >> .../percent-ok.log 2>&1 +\%Y-\%m-\%d
* * * * * /bin/date >> .../percent-trap.log 2>&1 +%Y-%m-%d
After three minutes:
$ cat percent-ok.log
2026-10-20
2026-10-20
2026-10-20
$ cat percent-trap.log
Three blank lines. The unescaped job ran — the file
grew on schedule — but cron split its command at the
first %, so what actually executed was just
/bin/date + (a format string of "+" and nothing else
prints an empty line), while Y-%m-%d was delivered to
date's standard input, which it politely ignored.
The daemon's log even shows the amputation — it
records the command it ran, not the one you wrote:
CRON[7251]: (root) CMD (/bin/date >> .../percent-trap.log 2>&1 +)
Everything past the % is simply not in the log line,
because as far as cron is concerned it was never part
of the command.
The fix
Escape every literal percent as \%:
0 4 * * * tar czf /backups/app-$(date +\%Y\%m\%d).tar.gz /srv/app
The backslash is consumed by cron, and the command
sees a plain %. This applies everywhere in the
command field — date formats, printf formats,
strftime-style strings in other tools.
Check: after any crontab edit that touches %, find
the job's CMD line in the log after its next run and
read what's actually there. If the line ends earlier
than your command does, an unescaped % is why.