Bash mktemp: Create Temporary Files and Directories Safely

Tested on RHEL 10.2 (Coughlan)
Package bash 5.2.26-6.el10
coreutils 9.5-8.el10_2
Applies to GNU/Linux distributions with Coreutils mktemp
Privilege Normal user
Scope GNU mktemp for temporary files and directories: templates, --suffix, TMPDIR, permissions, failure handling, trap EXIT cleanup, multi-file temp workspaces, why mktemp -u is unsafe, and atomic replacement on one filesystem. Does not cover full signal-trap reference or every TMPDIR policy on shared systems.
Related guides Bash shell script tutorial
Shell scripting course hub
Short on time?Beta Get the gist of this 9-minute read in seconds, then ask follow-up questions

mktemp creates a uniquely named temporary file or directory in one operation and prints its path. In Bash scripts, pair it with an EXIT trap so temporary data is removed even when the script exits early.

Avoid predictable paths such as /tmp/myapp.$$. Safe temporary-file creation requires choosing the name and creating the object without a race window between those steps.


Why Predictable /tmp Names Are Unsafe

A predictable name such as /tmp/myapp.$$ looks convenient:

bash
echo "/tmp/myapp.$$"
output
/tmp/myapp.14122

A predictable temporary path is unsafe because another process may create the path before your script opens it, potentially as a symlink or another object your script did not create. A separate "does this name exist?" check makes the race even more obvious, but avoiding that check alone does not make predictable temporary names safe.

mktemp selects and creates the object in one operation. GNU documents that as the secure alternative to generating a pathname separately and opening it later.

Timestamps, $RANDOM, and long UUID-like strings in the filename do not fix the race unless creation is atomic the way mktemp performs it.


Create a Temporary File with mktemp

With no arguments, GNU mktemp creates a file under $TMPDIR when that variable is set, otherwise under /tmp:

bash
tmpfile=$(mktemp)
stat -c '%A %a %n' "$tmpfile"
output
-rw------- 600 /tmp/tmp.jneXbvW49q

GNU mktemp creates files with user read/write permission (u+rw), further restricted by the current umask. With a typical umask, that produces mode 600 (rw-------). Always quote "$tmpfile" when you use the path.

Register cleanup as soon as the path is valid. The Bash error handling guide shows the same trap EXIT pattern for temp directories:

bash
tmpfile=$(mktemp) || exit 1
trap 'rm -f "$tmpfile"' EXIT

Create a Temporary Directory with mktemp -d

Use -d when the script needs a workspace rather than a single file:

bash
tmpdir=$(mktemp -d)
stat -c '%A %a %n' "$tmpdir"
output
drwx------ 700 /tmp/tmp.lSjZW6pD2l

GNU mktemp creates directories with user read/write/search permission (u+rwx), further restricted by umask. With a typical umask, that is mode 700. A temp directory is often cleaner when the workflow creates several intermediate files, downloads, or validation artifacts.

Prefer a temporary directory when a script creates several related intermediate files. One EXIT trap can then remove the entire workspace instead of tracking each temporary file separately.


Use Templates and TMPDIR

GNU Coreutils mktemp options you are most likely to use in scripts:

Option Purpose
-d, --directory Create a directory
-p DIR, --tmpdir=DIR Create relative to a specific temp directory
--suffix=SUFFIX Add a suffix
-q, --quiet Suppress creation-error messages
-u, --dry-run Print an unused name without creating it; unsafe for temp creation

A GNU mktemp template needs at least three consecutive X characters in its final path component. The final run of X characters is replaced with random alphanumeric characters; a suffix such as .txt may follow it. mktemp creates only the final component; parent directories must already exist.

bash
mktemp /tmp/demo.XXXXXX
output
/tmp/demo.y3Y8hP

Add a file extension with --suffix

With an explicit suffix, the template must end in X. An explicit --suffix value cannot contain /:

bash
tmpfile=$(mktemp --suffix=.txt)
echo "$tmpfile"
output
/tmp/tmp.K9DOKqgp5t.txt

Here .txt is the explicit suffix GNU appends after replacing the final X run.

Without --suffix, GNU infers one by locating the last X in the template. A descriptive basename can combine --tmpdir with a template whose extension follows the X run:

bash
mktemp --tmpdir=/tmp report.XXXXXX.txt
output
/tmp/report.UNu0kL.txt

Here GNU infers .txt as the suffix because the supplied template does not end in X.

Point TMPDIR at an existing directory when you want temporary files under a workspace you control. This standalone example removes the file and directory explicitly afterward:

bash
tmpbase=$(mktemp -d)
tmpfile=$(TMPDIR="$tmpbase" mktemp)
echo "$tmpfile"
rm -f "$tmpfile"
rmdir "$tmpbase"
output
/tmp/tmp.ZQkU1kyHXO/tmp.MrlZforqL9

--tmpdir[=DIR] treats the template as relative to DIR. With bare --tmpdir, GNU mktemp uses $TMPDIR when set and otherwise /tmp—the same parent-directory logic as a no-argument call. A relative template without --tmpdir is created in the current directory instead. Pin DIR when you want a predictable location:

bash
mktemp --tmpdir=/tmp bash-mktemp.XXXXXX
output
/tmp/bash-mktemp.Vjjr3t

Quote every returned path. Paths can contain characters that word-splitting or globbing would mishandle.


Check Creation Failure

mktemp prints the new path on success and exits non-zero when it cannot create the object. A bad template fails immediately:

bash
mktemp /tmp/no-x-suffix 2>&1; echo "exit: $?"
output
mktemp: too few X's in template '/tmp/no-x-suffix'
exit: 1

Under set -e, a failed mktemp stops the script unless you handle the status. The Bash exit codes article covers testing $? when you need a custom message instead of errexit alone.

Typical pattern with Bash strict mode enabled:

bash
set -euo pipefail
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

If mktemp fails, the assignment returns non-zero, errexit exits, and the EXIT trap has nothing to remove yet.


Clean Up with trap EXIT

Duplicating rm on every error branch is brittle. Register one EXIT trap right after successful creation:

bash
#!/usr/bin/env bash
set -euo pipefail
tmpdir=$(mktemp -d)
cleanup() { rm -rf "$tmpdir"; printf 'cleaned %s\n' "$tmpdir" >&2; }
trap cleanup EXIT
echo "working in $tmpdir"
echo data > "$tmpdir/file.txt"
false

When the final command fails, the trap still runs:

output
working in /tmp/tmp.Uee13cetMy
cleaned /tmp/tmp.Uee13cetMy

An EXIT trap runs when the shell exits normally or because of exit / set -e. SIGKILL cannot be trapped, so no shell cleanup can run after it. For signals such as INT and TERM, install handlers that exit with the appropriate status and let the EXIT trap perform cleanup:

bash
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

Exiting from the INT handler runs cleanup once through the EXIT trap. Do not call cleanup from both INT and EXIT.


Why mktemp -u Is Unsafe

GNU mktemp -u prints a currently unused temporary name but does not create anything:

bash
name=$(mktemp -u /tmp/unsafe.XXXXXX)
echo "name=$name"
test -e "$name" && echo "exists" || echo "does not exist"
output
name=/tmp/unsafe.bihuGI
does not exist

You are back to the two-step race: another process can create that path before your script does. The GNU manual documents -u as unsafe for secure temp creation. Do not use it when you need a real temporary object.


Temporary Files and Sensitive Data

mktemp protects temporary-file creation from pathname races; it does not encrypt the contents. Avoid putting secrets in filenames or command-line arguments, and remember that privileged users may still inspect temporary data.

Files your script creates inside a temporary directory use their own creation modes and the process umask:

bash
tmpdir=$(mktemp -d)
echo secret > "$tmpdir/data"

The directory itself is restrictive, but the inner file is created by the shell redirection according to the normal open mode and umask—not by mktemp.


Practical Example: Build Then Atomically Install a File

A common pattern writes and validates on the same filesystem as the final path, then renames into place so readers never see a half-written target file. Use a real destination directory—not a throwaway mktemp -d workspace—and create the temporary file inside it:

bash
#!/usr/bin/env bash
set -euo pipefail
install_dir="$HOME/.local/share/myapp"
final="$install_dir/config.conf"
mkdir -p "$install_dir"
tmpfile=$(mktemp --tmpdir="$install_dir" .config.conf.XXXXXX)
trap 'rm -f "$tmpfile"' EXIT
printf 'key=value\n' > "$tmpfile"
grep -q '^key=' "$tmpfile"
chmod 644 "$tmpfile"
mv -f "$tmpfile" "$final"
trap - EXIT
printf 'installed to %s\n' "$final"
output
installed to /home/testuser/.local/share/myapp/config.conf

Set the desired permissions on the temporary file before the rename. The installed file keeps the temporary file's metadata; mktemp does not copy the mode from an existing destination file. With a typical umask, mktemp alone leaves mode 600—fine for a private config, but adapt the chmod line when you replace a file that should be world-readable.

After a successful rename, $tmpfile no longer exists, so clear the EXIT trap with trap - EXIT.

Creating the temporary file in the destination directory keeps it on the same filesystem as final, allowing mv to use a rename instead of falling back to cross-filesystem copy/remove. On a filesystem with normal atomic rename semantics, readers see either the old file or the new file rather than a partially written destination.


Common mktemp Mistakes

Mistake Why it hurts
/tmp/app.$$ or timestamp names Predictable path + unsafe open semantics
mktemp -u then touch or mkdir Reintroduces the race mktemp avoids
Template path with missing parent dir GNU mktemp does not create parent directories
Temp file in /tmp, final file elsewhere mv may copy across filesystems instead of atomic rename
Unquoted $tmpfile Word-splitting or globbing breaks paths
Cleanup only on success Error paths leak files without trap EXIT
cleanup in both INT and EXIT traps Cleanup runs twice on Ctrl+C
Fewer than three X characters in the final X run GNU mktemp rejects the template

Summary

mktemp creates a temporary file or directory in one step, which is the property that makes it safer than predictable /tmp names built from $$, timestamps, or $RANDOM. Use mktemp for a single file, mktemp -d for multi-file work, templates with at least three consecutive X characters in the final path component, --suffix when you need an extension, and TMPDIR or --tmpdir when you want a specific parent directory.

Quote the returned path, exit when creation fails, and register trap EXIT cleanup immediately after success. Avoid mktemp -u for real temp objects. For atomic replacement, create the temp file in the destination directory on the same filesystem, set permissions before rename, validate, then mv it into the final name.


References


Frequently Asked Questions

Is mktemp secure?

It is secure against the classic temp-file race when you use it to create the object (not mktemp -u). It does not encrypt contents or hide data from privileged users on the system.

What permissions does it use?

GNU mktemp creates files with user read/write permissions and directories with user read/write/search permissions, further restricted by the current umask. With a typical umask, that produces 600 files and 700 directories.

What do the X characters mean?

The final run of X characters in the template is replaced with random alphanumeric characters. That run must contain at least three consecutive X characters, and a suffix such as .txt may follow it.

Why avoid mktemp -u?

-u prints a currently unused name but does not create anything, so another process can claim the path before your script does.

How do I clean up after Ctrl+C?

Register trap cleanup EXIT right after mktemp, then use trap 'exit 130' INT and trap 'exit 143' TERM. The signal handler sets the exit status; exiting runs cleanup once through the EXIT trap.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)