SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

umask: where Linux default permissions begin

The umask decides the mode of every file you create. Print it, create a file and a directory, read the modes back, and see why root and your user differ.

What the umask does on Linux

The umask is a number carried by every process on Linux, and it decides the mode of each file and directory that process creates. A program asks the kernel for a set of permissions at creation time. The kernel clears every bit the mask names and applies what is left. A umask never grants access. It only takes bits away from what the creating program asked for.

The value is not a property of your distribution. It depends on which account you are, and on how the shell was started. Those two answers can differ on one machine, at the same moment, on a stock image. So the first step is not a lookup in a manual. It is a measurement on the box in front of you.

umask
umask -S

The first form prints the mask in octal. The second prints the same mask as the permissions it allows, in the symbolic form chmod accepts. Keep both lines on screen. Everything below is a comparison against what your shell just printed.

umask is a shell builtin and not a program on disk. Confirm that with type umask. It matters, because a builtin changes the shell process itself. A separate program could only change its own process, then exit and take the change with it.

Create a file and a directory, then read the modes back

cd "$(mktemp -d)"
touch probe.file
mkdir probe.dir
stat -c '%a %A %n' probe.file probe.dir

%a prints the mode in octal and %A prints the same mode in the drwxr-xr-x form that ls -l uses. Compare both lines against the mask you printed a moment ago. Every bit set in the mask is missing from the modes, because clearing those bits is the only thing a mask does. If the %A column is not yet obvious to read, the drwxr-xr-x permission string is the piece to have straight first.

The file and the directory differ from each other, and the mask is not the reason for that difference. touch asks the kernel for read and write for owner, group and other. mkdir asks for read, write and execute for all three. The same mask is subtracted from two different requests. So a file created by touch is never executable, whatever the mask holds: the execute bit was never requested, and a mask cannot add a bit back.

( umask a=rwx; touch open.file; stat -c '%a %A %n' open.file )

Those parentheses run the commands in a subshell, so the change dies with it. The mask now asks for nothing to be cleared, and stat still reports no execute bit on the file. Run umask again afterwards and your original value is back, which shows the setting lives inside a process and is inherited by children rather than stored anywhere on disk.

Directories are where a cleared execute bit is felt.

( umask a=rw; mkdir noexec.dir; cd noexec.dir )

As an ordinary user that cd fails with bash: cd: noexec.dir: Permission denied, because the mask cleared the execute bit mkdir asked for, and a directory without the execute bit cannot be entered. Root skips that check, so this one only shows itself on a normal account.

Why root and your own user see a different umask

Run the same measurement through another account, started another way, and read the two outputs side by side.

umask
sudo -i umask

sudo -i starts root's login shell and runs the builtin inside it, so this is a different account arriving through a different startup path. On stock Ubuntu and Debian server images the two lines can print different values. Both lines are correct. Each shows what its own startup path produced, and the rest of this post is about which part of the system produced it.

Which file on your image decided the value

grep -nE '^(UMASK|USERGROUPS_ENAB)' /etc/login.defs
grep -rn pam_umask /etc/pam.d/
grep -rn umask /etc/profile /etc/profile.d/ /etc/bash.bashrc ~/.profile ~/.bashrc 2>/dev/null || echo 'no umask line in the shell startup files'

If the first grep prints nothing, run it again without the ^ anchor: the line may be commented out, and a commented line is documentation rather than configuration. The third grep is the one that surprises people. On Debian and Ubuntu the shipped /etc/profile mostly points at PAM instead of setting a mask itself, so the file you assumed was in charge often is not. grep exits non-zero when it matches nothing, which is why that line ends in || echo: on an image where no startup file mentions a mask you get the message instead of silence, and the message is the finding.

/etc/login.defs advertises a value and PAM applies one

The UMASK line in /etc/login.defs is the value most guides quote. Neither the kernel nor the shell reads that file. It is read by pam_umask, a PAM (pluggable authentication modules) module that runs when a session is created. pam_umask takes the first value it finds: a umask= entry in the user's GECOS field, then a umask= argument written on the pam_umask.so line itself, then UMASK from /etc/login.defs. Distributions patch this module, so run man pam_umask on your own image and read the order printed there.

That is how /etc/login.defs can advertise one value while your session ends up with another, with no warning printed on either side. The greps tell you which case you are in. If the pam_umask.so line carries its own umask= argument, the login.defs line lost.

USERGROUPS_ENAB and the root exception

id -un
id -gn

If those two print the same name, you have a user private group: the account was created with a group of its own, named after it. pam_umask has a usergroups behaviour, controlled by USERGROUPS_ENAB in /etc/login.defs. When it is on, and the account is not root, and the primary group name matches the user name, the module copies the owner digit of the mask into the group digit. The session then ends with a mask that leaves the group bits open on everything that account creates. Root is excluded by the module itself, and that exclusion is the most common single reason two shells on one server print different masks.

The reasoning behind the rule is that a private group has exactly one member, so group-writable means owner-writable and nothing more. That holds until somebody adds a second member to the group. From that moment every file the account has ever created is writable by the new member, and no command was run on those files to make it so. Give each service its own least-privilege user account and that group stays a group of one on purpose.

Login shell, non-login shell and non-interactive shell

umask
bash -lc 'umask'
bash -c 'umask'

PAM runs when a session is created: login on the console, sshd, su, sudo -i. It does not run when one shell starts another shell. bash -l is a login shell, so it reads /etc/profile and ~/.profile, but it never calls pam_umask, because no session was created. bash -c reads neither file and inherits the mask of the process that started it. A cron job, a git hook and a program started by a service manager all sit in that last case, so their mask is whatever their parent process had.

This is why "I set it in /etc/profile and the service still writes the wrong mode" is such a common report. The service never read that file.

Where to set it so it survives

Set the mask where the workload actually starts, because each start path reads a different file.

  1. For accounts that log in: UMASK in /etc/login.defs, applied by pam_umask to every session on the machine. It is machine-wide, so it moves every account at once.
  2. For one account: the umask= argument on the pam_umask.so line is machine-wide too, so a per-user value belongs in that user's GECOS field, or in ~/.profile for login shells and ~/.bashrc for interactive ones.
  3. For a daemon under systemd: UMask= in the unit's [Service] section. A unit is started by the service manager, so /etc/profile is never read and pam_umask never runs. The unit file is the only one of these that reaches the daemon.
  4. For a script started by cron or by a hook: an explicit umask on the first line, before the script creates anything.
[Service]
UMask=<the octal mask you chose>

Then verify from a fresh start of that same path, and never from the shell where you edited the file. Your current shell already holds its mask, and editing a config file does not reach back into a running process.

bash -lc 'umask'
sudo -i umask

Why chmod afterwards is not the same fix

chmod repairs files that exist. The mask decides the mode of files that do not exist yet. Run chmod -R over a directory and the next file the service writes arrives with the old mode again, because that mode came from the creating process and nothing about the directory changes it.

There is also a window. Between the moment a file is created and the moment chmod runs, the file sits on disk with the wider mode, and any process that can read the directory can open it. For a private key or a backup archive, that window is the risk you were trying to remove.

Set the mode at creation instead. install -m u=rw,go= newfile /etc/app/newfile writes the destination with an explicit mode, and mkdir -m does the same for a directory. Both take the mode you name and ignore the mask. ssh-keygen sets the mode on the private key it writes, which is why that one file is often correct on a machine where everything else is not.

The usual casualty is SSH. A ~/.ssh made with a plain mkdir, or an authorized_keys appended with cat >>, takes the mask of your shell. With StrictModes on, sshd refuses to read a key file out of a group-writable directory. The client is told Permission denied (publickey) while the server's /var/log/auth.log records the real cause:

Authentication refused: bad ownership or modes for directory /home/deploy/.ssh

That check is deliberate, and hardening SSH on a VPS depends on it holding. Measure the mask on a new server before you create the accounts that will use it, alongside the first ten minutes on a new VPS, and the mode of every file those accounts write is settled in advance.

Copies and archives ignore the mask

cp -p and rsync -a restore the mode recorded on the source file, so the mask gets no say in the result. tar does the same when it extracts as root, or as an ordinary user with -p. A file restored from a backup keeps the mode it had when the backup was made. Check that before you conclude that a correct mask is being ignored: for restored data it was never consulted.

FAQ

Why does my cron job create files with a different mode than my ssh session?

A cron job is not a login session, so pam_umask never runs for it, and it reads neither /etc/profile nor ~/.profile. It inherits the mask of the process that started it. Put an explicit umask on the first line of the script, before it creates anything, and print the mask once from inside the job so you can see what that job really has rather than what your own shell has.

Why does /etc/login.defs say one thing and my shell print another?

UMASK in /etc/login.defs is only the last fallback for pam_umask. The module prefers a umask= entry in the user's GECOS field, then a umask= argument on the pam_umask.so line in /etc/pam.d/. The usergroups behaviour, switched on by USERGROUPS_ENAB, then rewrites the group digit for any non-root account whose primary group is named after it. Run grep -rn pam_umask /etc/pam.d/ and id -un; id -gn to see which of those applies to your account.

Can a umask make a file executable?

No. A mask can only clear bits from what the creating program asked for. touch never asks for the execute bit, so no mask produces an executable file. Confirm it in a throwaway directory with ( umask a=rwx; touch f; stat -c '%a %A' f ). To get an execute bit you need chmod, or a program such as install -m that asks for it at creation time.

Where do I set the umask for a systemd service?

In the unit, with UMask= in the [Service] section. A service is started by the service manager rather than by a login, so the shell startup files are never read and pam_umask never runs for it. After systemctl daemon-reload and a restart of the unit, confirm from outside: let the service create a file, then read the result with stat -c '%a %n'.

Is a group-writable default mask safe?

It is safe while the group has exactly one member, which is the assumption behind the user private group scheme. Add a second account to that group and every file the first account has created becomes writable by the new member at once, with no command run on those files. Run id -un and id -gn: printing the same name means you are on a private group. If you share a group between accounts, set a mask that clears the group write bit, then create a file and read stat -c '%a %n' to prove the change took effect.

#umask#permissions#pam#login-defs#linux