Every directory that is writable by everyone is a gift to anyone who has gained a foothold, and /tmp is the most generous gift on a default system: world-writable, world-readable, and mounted right onto the root filesystem where files land persistently. The canonical intrusion pattern is old enough to be a museum piece, download a payload to /tmp, mark it executable, run it, and the noexec and nosuid mount options exist to break precisely that pattern at the kernel level. This article builds the hardening on a real server: what each option closes, the tmpfs and fstab configurations in current distributions, the honest list of applications that break and their standard fixes, the per-service escape through systemd, and the rollout that ends with a /tmp that attackers can still write to but can no longer run from.
What the options close and what they do not
Three mount options form the hardening set, and each names the attack it closes. Noexec tells the kernel to refuse execution for every file on the filesystem, so the payload downloaded and marked executable returns Permission denied the moment its author tries to run it. Nosuid tells the kernel to ignore setuid and setgid bits on files, so a copied root shell with the setuid bit, the oldest escalation toy in the book, executes as the calling user and gains nothing. Nodev refuses to interpret device files, so the attacker cannot craft a block or character device pointing at raw memory or disk. The three together close the execute-and-escalate pattern completely, and the honest limit of the set belongs in the same paragraph: noexec stops direct execution, but a determined local user can still invoke an interpreter explicitly, /lib64/ld-linux /tmp/payload, which is why the options are a layer on a hardened system rather than a wall on their own.
The sticky bit, that 1777 mode on the directory, belongs to the baseline rather than to the options: it prevents users from deleting each other's files in a shared directory, and it is present by default everywhere current. The hardening this article adds changes what files can do, not who can create them, which preserves the legitimate contract that software holds with its scratch space while removing the two properties that make /tmp attractive as a launch pad.
The tmpfs route and the fstab entry
The modern shape of a hardened /tmp on most distributions is a tmpfs, a memory-backed filesystem, mounted with the restrictive options from the first moment. The systemd-managed route is one command on most current systems:
systemctl unmask tmp.mount && systemctl enable --now tmp.mount
which activates the unit that ships with systemd and mounts /tmp as tmpfs with mode 1777 and noexec, nosuid, nodev already in its default options. The unit file, /usr/lib/systemd/system/tmp.mount, is the reference for what the defaults are, and an override through systemctl edit tmp.mount customizes them, most commonly the size limit, where the default is half of RAM and a busy server sometimes wants an explicit ceiling:
[Mount]
Options=mode=1777,strictatime,nosuid,nodev,noexec,size=2G
The fstab route achieves the same result on systems where the traditional file is the interface, and its single line reads:
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,mode=1777,size=2G 0 0
Both routes produce the same runtime state, visible in findmnt /tmp, which prints the mount options in force and is the command that ends every configuration debate about whether the hardening actually applied. The choice between the routes is a management preference: the fstab line is the classic, portable and readable, the systemd unit is the native on systems that manage everything through the init, and either way the verification is the findmnt output rather than the file that produced it.
tmpfs consequences, /var/tmp and the two-tier scratch model
Moving /tmp to tmpfs changes three properties that deserve their own planning. Memory backs the filesystem, so large temporary files consume RAM rather than disk, which is exactly what the size limit exists to bound, and a runaway process that filled a 2-gigabyte tmpfs now fails with a disk-full error against a limit the administrator chose. Contents vanish on reboot, which for /tmp is semantically correct, the directory is temporary by contract, but breaks the rare software that treats it as durable storage, and the fix for those cases belongs in the next section. And the swap relationship: tmpfs pages can swap under memory pressure, which means the contents behave like RAM with a disk-backed safety net rather than pure RAM.
The sibling directory /var/tmp completes the two-tier model, and hardening it correctly matters as much as the /tmp part. The Filesystem Hierarchy Standard gives /var/tmp the same shared-scratch role with one difference: contents survive reboot. Attackers know this too, and a payload parked in /var/tmp waits for a reboot instead of dying with it. The same option set belongs on /var/tmp, as a bind mount from the root filesystem with noexec,nosuid,nodev or as a separate small filesystem, and the findmnt check covers both directories in one pass. The hardened pair reads: /tmp, volatile and sealed, /var/tmp, persistent and equally sealed, and the difference between them is lifetime rather than permissiveness.
What breaks and the standard repairs
A short, honest list of software breaks under noexec, and each entry has a standard repair that keeps the hardening in place. Java tooling is the classic: some build and install tools extract a helper executable into the temporary directory and run it from there, and the failure appears as an exec format or permission error deep inside a build log. The repair is a private, executable-capable scratch directory for the affected process, exported through TMPDIR, the environment variable that most well-behaved software honors for exactly this reason:
export TMPDIR=/var/tmp/build-scratch
Installers are the second family, and their repair is the same: point TMPDIR at a location where execution is permitted, run the installer, done. The third family is software with hard-coded /tmp paths, rarer every year but never extinct, and the repair for that family is the bind-mounted escape hatch, a small directory mounted with exec rights for the specific application, which trades a documented exception for the blanket rule and keeps the exception visible in fstab rather than in folklore.
The diagnostic skill that pays for the whole section is recognizing the noexec failure signature in logs: an error mentioning exec, Permission denied, or the interpreter failing on a file that exists and is marked executable is the mount option doing its job, while an error about a file not existing is an ordinary path problem. Ten minutes of pattern recognition turns the hardening from a mystery breakage source into a configuration the team understands and maintains.
The systemd PrivateTmp route for services
The per-service answer to the shared-directory problem is systemd's PrivateTmp, and on a server it deserves as much attention as the mount options themselves. One line in a unit file:
[Service]
PrivateTmp=yes
gives the service its own private /tmp and /var/tmp, namespaces invisible to every other process, and the combination with the global hardening is where the design gets genuinely clean: services that legitimately need scratch space get their own, isolated, while the shared directories stay sealed for everything that did not ask. The property that makes PrivateTmp more than convenience: one compromised service cannot read the temporary files of another, the session tokens and upload intermediates that historically made the shared /tmp an eavesdropping paradise, and the isolation is a kernel-level namespace, not a permission arrangement that a future chmod can undo.
The operational note for PrivateTmp is cleanup: the private directories, under /tmp/systemd-private-*, live as long as the service and are removed when it stops, which means a crashing service leaves its scratch behind until the next restart, and a scheduled cleanup or a service restart handles the accumulation. Used together, the three layers, sealed global /tmp, sealed /var/tmp, private per-service scratch, give every workload the scratch space it needs while the classic /tmp attack gets no purchase anywhere on the machine.
Rollout order and the field checklist
The rollout that avoids a self-inflicted outage follows the standard change discipline. Audit first, findmnt /tmp and findmnt /var/tmp, to know the current state. Stage the change on a non-critical host, and run the real workload against it for a day, because the noexec failure signature in a build log is worth discovering while the change is still an experiment. Apply to production hosts with the TMPDIR repairs prepared for the known-affected software, and keep a one-line rollback in the change ticket, the fstab line commented or the unit disabled, because the rollback of a mount option is a remount away. The monitoring angle completes the rollout: disk-full alerts must cover the tmpfs size limits, and the first candidate for the new limit is always the process that legitimately used the old, unbounded space.
The checklist compresses the article into the order of an afternoon. Findmnt both directories, harden both with the three options, tmpfs for /tmp with an explicit size, persistent-but-sealed for /var/tmp, PrivateTmp for every service that touches scratch, TMPDIR repairs documented for the affected software, staging pass with the real workload, and findmnt again as the proof. The server that passes that final check has converted its most-abused directory from a launch pad into a waiting room, writable as ever, readable as ever, and useless to anyone whose plan ended with the word execute.
## The threat pattern in concrete terms
The hardening reads better with the attack it stops described in its actual shape. The web server compromise: a vulnerable application lets someone upload a file, the file lands in /tmp because that is where upload handlers stage incoming data, and the follow-up command tries to execute it right where it landed. With noexec the execution fails, and the attacker's next best option is moving the file to an executable filesystem, which requires write access somewhere else, a far higher bar than the world-writable directory that made the pattern attractive. The cron persistence variant: a payload writes a script into /tmp and registers a scheduled task to run it, and the failure signature, the cron log filling with permission errors at the scheduled minute, is the hardening working at 3 a.m. while everyone sleeps.
The setuid variant completes the picture: the intruder copies a setuid-root binary, a copy of a system tool that carries the bit, into /tmp and runs it as an ordinary user, expecting the file's bit to grant root. Nosuid turns that expectation into a plain run-as-user, and the copied root shell becomes a paperweight in a world-readable directory. Each of the three stories is decades old, each is still attempted weekly on public systems, and each dies at the mount layer without a single line of application code being involved, which is the quiet argument for doing this hardening early rather than eventually.
Changing the options on a running system
The remount trick applies new mount options to an already-mounted filesystem without unmounting it, and it is the command that turns a planned reboot into a planned minute:
mount -o remount,noexec,nosuid,nodev /tmp
applies the trio to a live /tmp immediately, processes using the directory continue unaffected, and the effect on new execution attempts is instant. The remount is also the honest test of the fstab or unit change before any reboot: apply by remount, verify with findmnt, run the workload, and only then let the reboot confirm that the persistent configuration matches the runtime one. The two-step pattern, remount now for effect, fstab for persistence, closes the gap that catches careless teams, a hardened runtime that reverts at the next boot because only one of the two was written. Findmnt after the reboot, against findmnt before it, is the thirty-second check that proves both halves exist.
The closing perspective on the whole topic: /tmp hardening costs one afternoon, breaks almost nothing on a properly configured server, and closes the oldest class of local attacks in the book. The systems that skip it are not systems that weighed the tradeoff, they are systems that never ran the findmnt command, and the difference between the two is visible in the first line of any post-incident report that starts with a payload found in a world-writable directory.