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 02/var/tmp /var/tmp none bind,nosuid,nodev,noexec 0 03tmpfs /dev/shm tmpfs rw,nosuid,nodev,noexec 0 0Then 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/tmpgetsPermission deniedwhen trying to start it.nosuid: setuid and setgid bits and file capabilities are ignored. A setuid-root copy of a program in/tmpruns 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/tmpand 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 /tmpIf 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/fstabcan stop it from booting normally.
Step 1: Backup /etc/fstab
shell
1cp -a /etc/fstab /etc/fstab.bakStep 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 0If 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-reload2findmnt --verifyThe 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 /tmpStop those services, then mount the new /tmp:
shell
1mount /tmpIf /tmp was already a tmpfs from tmp.mount, apply the new options with a remount instead:
shell
1mount -o remount /tmpThen 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 /tmpThe output should look like this:
shell
1TARGET SOURCE FSTYPE OPTIONS2/tmp tmpfs tmpfs rw,nosuid,nodev,noexec,relatime,size=2097152kNow check that noexec really works:
shell
1printf '#!/bin/sh\necho executed\n' > /tmp/test.sh2chmod +x /tmp/test.sh3/tmp/test.sh4rm /tmp/test.shInstead 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.mountAdd these lines in the editor and save:
shell
1[Mount]2Options=mode=1777,strictatime,nosuid,nodev,noexec,size=2G,nr_inodes=1mOptions= 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 /tmpThis 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=20482mkfs.ext4 -F /var/tempFS3chmod 600 /var/tempFSThen 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 0Run 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 /tmp2chmod 1777 /tmpdf -h /tmp should now show the new filesystem:
shell
1Filesystem Size Used Avail Use% Mounted on2/dev/loop0 2.0G 536K 1.8G 1% /tmpWhen 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/tmp2chmod 700 /root/tmp3TMPDIR=/root/tmp ./install.shIf 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 /tmp2./install.sh3mount -o remount /tmpRun 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 0If /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-reload2mount /var/tmp3findmnt /var/tmpThe 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 0If 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-reload2mount -o remount /dev/shm3findmnt /dev/shmThe 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"; doneEach line should include nosuid,nodev,noexec:
shell
1/tmp rw,nosuid,nodev,noexec,relatime2/var/tmp rw,nosuid,nodev,noexec,relatime3/dev/shm rw,nosuid,nodev,noexec,relatimeThis 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.
