A script that creates files with the expected permissions when run by hand, but with different permissions once executed by cron, almost always points to a different umask between the two contexts, rarely a bug in the script itself.
What umask actually does
umask defines a bitmask subtracted from the default permissions when a file or directory gets created, never applied retroactively to an existing file. A file created with no execute permission starts from a base of 666 (read/write for everyone), a directory starts from a base of 777 (execute on a directory means “allowed to enter it”), and umask strips the corresponding bits from that base.
# umask 022: strips write for group and others
umask
# 0022
# File created: 666 - 022 = 644 (rw-r--r--)
touch file.txt && ls -l file.txt
# Directory created: 777 - 022 = 755 (rwxr-xr-x)
mkdir dir && ls -ld dir
The trap: files and directories don’t react the same way
The same umask produces a different result depending on whether a file or a directory gets created, a frequent source of confusion for anyone reasoning purely in terms of a subtracted percentage rather than bits stripped from the actual base (666 versus 777). A umask 022 strips group/other write permission in both cases, but the execute bit only ever disappears where it wasn’t there to begin with, meaning never on a plain file created by a shell redirection or touch.
Why cron rarely inherits the shell’s umask
An interactive shell typically inherits the umask configured in /etc/profile or the user’s own startup file (022 being a common value). cron, by contrast, runs jobs in a minimal environment that doesn’t source those startup files: without an explicit UMASK directive in the crontab itself, the effective value can differ from the interactive shell’s, producing files more or less permissive than what the script expected when tested by hand.
# Explicit umask in the crontab,
# independent of any shell startup file
UMASK=022
0 3 * * * /usr/local/bin/backup.sh
Takeaway
umask subtracts bits from a 666 (files) or 777 (directories) base at creation time, never afterward, a distinction that explains why the same mask produces visually different results between a file and a directory. cron doesn’t automatically inherit the umask configured for an interactive shell, since it never sources the same startup files: a script with correct permissions in manual testing but inconsistent ones once scheduled points to this gap, an explicit UMASK directive in the crontab fixing the problem at the source rather than patching permissions after the fact.