Securing /tmp on a linux server

Published Updated

9 min read

One of the first things we do here at ITsyndicate is to secure /tmp /var/tmp and /dev/shm to prevent common exploits and rootkits from having their way with a server. This method doesn’t prevent users from uploading content to those directories, but it disables their direct execution and makes the setuid bit on anything stored there useless.


The short version: mount all three directories with the nosuid, nodev, and noexec options. On most servers, that means three lines in /etc/fstab:

shell

1tmpfs /tmp tmpfs rw,nosuid,nodev,noexec,size=2G,mode=1777 0 0
2/var/tmp /var/tmp none bind,nosuid,nodev,noexec 0 0
3tmpfs /dev/shm tmpfs rw,nosuid,nodev,noexec 0 0

Then mount them and check the result with findmnt. The rest of this guide goes through each directory step by step, shows the systemd way of doing it, and explains what these options do and don't protect you from. The commands were tested on Debian 13 and Ubuntu 24.04.

What nosuid, nodev, and noexec do

  • noexec: the kernel refuses to run programs directly from this filesystem. An attacker who drops a binary or a script into /tmp gets Permission denied when trying to start it.
  • nosuid: setuid and setgid bits and file capabilities are ignored. A setuid-root copy of a program in /tmp runs with the rights of the user who started it, not as root.
  • nodev: device files on this filesystem don't work. Nobody can create a device node in /tmp and use it to read a disk or memory directly.

One caveat: noexec only blocks direct execution. sh /tmp/script.sh or python3 /tmp/script.py still works, because the program that runs is the interpreter from /usr/bin, not the file in /tmp.

So treat these options as one layer of defense. They stop a lot of automated drop-and-run attacks, but they don't replace patching your web apps, running services as unprivileged users, or PrivateTmp= in systemd units.

Securing /tmp directory

This directory is used by Apache, MySQL and PHP, among others, to store temporary data as well as lock files and sockets. You have probably seen a lot of session files and the mysql.sock file under it, but sometimes attackers can upload and execute exploits with a PHP injection via Apache.

Start by checking what you have now:

shell

1findmnt /tmp

If the command prints nothing, /tmp is a plain directory on the root filesystem. That's the default on Ubuntu 24.04 LTS and Debian 12.

If it shows tmpfs, systemd already mounts /tmp in memory with its tmp.mount unit. That's the default on Debian 13, Ubuntu 24.10 and newer (including 26.04 LTS), and Fedora. The unit sets nosuid and nodev, but not noexec, so you still need the steps below.

⚠️ Do this in a maintenance window. When you mount a new filesystem on top of /tmp, everything in the old directory disappears from view, including sockets and lock files of running services like MySQL, PHP-FPM, or your web server. Those services keep using the hidden files and can break in odd ways. Stop them first, or reboot right after the change. Also make sure you have console access to the server: a typo in /etc/fstab can stop it from booting normally.

Step 1: Backup /etc/fstab

shell

1cp -a /etc/fstab /etc/fstab.bak

Step 2: Add a tmpfs line for /tmp to /etc/fstab

Add this line to /etc/fstab:

shell

1tmpfs /tmp tmpfs rw,nosuid,nodev,noexec,size=2G,mode=1777 0 0

If there's already a /tmp line, for example because your data center created a separate /tmp partition when it provisioned the server, don't add a second one. Add nosuid,nodev,noexec to the options of the existing line instead.

tmpfs keeps files in RAM (and in swap under memory pressure), so /tmp is fast and starts empty after every reboot. size=2G caps how much memory it can take. Without it, the limit is half of your RAM.

Pick a size that fits your server. A full /tmp breaks uploads and database temp files just as surely as a full disk. mode=1777 sets the usual /tmp permissions: everyone can write, but only the owner can delete their files.

A /tmp line in /etc/fstab takes precedence over systemd's tmp.mount, so this works the same way on every distribution.

Step 3: Test your fstab entry

shell

1systemctl daemon-reload
2findmnt --verify

The first command tells systemd about the new line. The second one checks the syntax of /etc/fstab and should end with Success, no errors or warnings detected.

Step 4: Mount the new /tmp

First, see which processes have files open in /tmp (install the lsof package if it's missing):

shell

1lsof +D /tmp

Stop those services, then mount the new /tmp:

shell

1mount /tmp

If /tmp was already a tmpfs from tmp.mount, apply the new options with a remount instead:

shell

1mount -o remount /tmp

Then start the services again. Or simply reboot the server, which is the cleanest way.

Step 5: Verify that your /tmp mount is working

shell

1findmnt /tmp

The output should look like this:

shell

1TARGET SOURCE FSTYPE OPTIONS
2/tmp tmpfs tmpfs rw,nosuid,nodev,noexec,relatime,size=2097152k

Now check that noexec really works:

shell

1printf '#!/bin/sh\necho executed\n' > /tmp/test.sh
2chmod +x /tmp/test.sh
3/tmp/test.sh
4rm /tmp/test.sh

Instead of executed, you should get a Permission denied error.

Alternative: change the options of systemd's tmp.mount

If /tmp is already mounted by tmp.mount and you'd rather not touch /etc/fstab, override the options of the unit:

shell

1systemctl edit tmp.mount

Add these lines in the editor and save:

shell

1[Mount]
2Options=mode=1777,strictatime,nosuid,nodev,noexec,size=2G,nr_inodes=1m

Options= replaces the whole list from the unit, so it repeats the defaults and adds noexec and your own size. The new options apply at the next boot. To apply them right away without unmounting /tmp, remount it:

shell

1mount -o remount,noexec,size=2G /tmp

This only works where the unit exists. On Ubuntu 24.04, tmp.mount isn't active by default, so use the /etc/fstab line from Step 2.

Alternative: a disk-backed /tmp in a loop file

Use this if the server is short on RAM or your apps write large temporary files, and you can't add a separate partition. It gives /tmp its own filesystem with the same mount options, stored in a file on disk.

We will be placing this file under /var, but you can choose whatever partition works best for you. These commands create a 2 GB file and format it with ext4:

shell

1dd if=/dev/zero of=/var/tempFS bs=1M count=2048
2mkfs.ext4 -F /var/tempFS
3chmod 600 /var/tempFS

Then add this line to /etc/fstab instead of the tmpfs line from Step 2:

shell

1/var/tempFS /tmp ext4 loop,rw,nosuid,nodev,noexec 0 0

Run systemctl daemon-reload and findmnt --verify. Here findmnt --verify warns that the source is a regular file, which is expected for a loop mount.

Stop the services that use /tmp and mount it. If /tmp is already a tmpfs from tmp.mount, reboot instead, so the loop file doesn't end up mounted on top of it. A fresh ext4 filesystem has 755 permissions on its root, so set the /tmp permissions once after the first mount:

shell

1mount /tmp
2chmod 1777 /tmp

df -h /tmp should now show the new filesystem:

shell

1Filesystem Size Used Avail Use% Mounted on
2/dev/loop0 2.0G 536K 1.8G 1% /tmp

When noexec gets in the way

Most of the time, you won't notice noexec. Package managers are fine: apt on Debian 12 and newer and on Ubuntu 24.04 doesn't run package scripts from /tmp anymore, because debconf extracts them into /var/cache/debconf instead. Older tips about setting APT::ExtractTemplates::TempDir fix a problem these releases no longer have.

The usual trouble is an installer or build script that unpacks a helper binary into /tmp and runs it from there. It fails with Permission denied. Most of these tools respect the TMPDIR variable, so point it to a private directory that allows execution:

shell

1mkdir -p /root/tmp
2chmod 700 /root/tmp
3TMPDIR=/root/tmp ./install.sh

If a tool ignores TMPDIR, allow execution on /tmp just for the install, then bring back the options from /etc/fstab:

shell

1mount -o remount,exec /tmp
2./install.sh
3mount -o remount /tmp

Run findmnt /tmp afterwards to make sure noexec is back.

If an app on the server can't live with noexec at all, remove noexec from the /tmp line in /etc/fstab and run mount -o remount /tmp. Keep nosuid and nodev: they almost never get in the way.

Securing /var/tmp

/var/tmp is world-writable too, so it needs the same options. But it has a different job. It's for temporary files that should survive a reboot, and it usually has more space than /tmp.

That's why it shouldn't be a symbolic link to /tmp. With a tmpfs /tmp, everything in /var/tmp would vanish on every reboot, and apps that write large files there would run out of space.

Instead, bind-mount /var/tmp onto itself. The files stay on disk where they were, and the mount gets its own nosuid,nodev,noexec flags. Because it's the same directory, nothing disappears, and you don't need to copy any data.

Step 1: Add a bind mount for /var/tmp to /etc/fstab

Add this line to /etc/fstab:

shell

1/var/tmp /var/tmp none bind,nosuid,nodev,noexec 0 0

If /var/tmp is already a separate partition, add nosuid,nodev,noexec to its existing line instead.

Step 2: Mount /var/tmp and check it

shell

1systemctl daemon-reload
2mount /var/tmp
3findmnt /var/tmp

The OPTIONS column should include nosuid,nodev,noexec. The SOURCE column shows the disk with the directory in brackets, like /dev/sda1[/var/tmp]: the same files with new mount flags.

Securing /dev/shm

/dev/shm is a tmpfs for shared memory. Programs use it to pass data to each other quickly, and everyone can write to it. systemd already mounts it with nosuid and nodev, but files there can still be executed. That makes it one more place where an attacker can drop and run a binary.

Step 1: Edit your /etc/fstab

On most current systems, there's no /dev/shm line in /etc/fstab, because systemd mounts it at boot. Add one:

shell

1tmpfs /dev/shm tmpfs rw,nosuid,nodev,noexec 0 0

If your /etc/fstab already has a /dev/shm line, add nosuid,nodev,noexec to its options instead.

Step 2: Remount /dev/shm

shell

1systemctl daemon-reload
2mount -o remount /dev/shm
3findmnt /dev/shm

The options should now include noexec. Software that needs to execute files from shared memory is rare. But if an app starts failing with Permission denied errors in /dev/shm after this change, this option is the reason.

Check all three mounts

Once everything is in place, reboot the server at a quiet moment and check that the options survived it:

shell

1for d in /tmp /var/tmp /dev/shm; do findmnt -no TARGET,VFS-OPTIONS "$d"; done

Each line should include nosuid,nodev,noexec:

shell

1/tmp rw,nosuid,nodev,noexec,relatime
2/var/tmp rw,nosuid,nodev,noexec,relatime
3/dev/shm rw,nosuid,nodev,noexec,relatime

This is also what the CIS Benchmarks for Linux check: /tmp on tmpfs or on its own partition, and nodev, nosuid, and noexec on /tmp, /var/tmp, and /dev/shm. Some CIS profiles go further and want /var/tmp on its own partition, which a bind mount doesn't give you. Control numbers differ between versions, so check the CIS Benchmark for your distribution.

You are done! Your /tmp, /var/tmp, and /dev/shm are secured. On a PHP server, a good next step is to disable dangerous PHP functions. And if you'd like a second pair of eyes on server hardening or a CIS baseline for your infrastructure, our cloud security and compliance team can help.

Alex Kondratiev

Alex Kondratiev

Founder and CEO of ITsyndicate. DevOps engineer with 15+ years in cloud infrastructure, Infrastructure as Code, Kubernetes and automation, focused on secure, scalable and resilient systems.

More about Alex

Stay online.
Stay in command.