mkdir Command in Linux: Complete Guide to Creating Directories and Parameters

mkdir command in Linux and it perimeters

mkdir doesn’t get much respect — it’s usually the first command people learn right after cd and ls, and it feels too simple to write a whole article about. But once I started working with permissions, umasks, and deployment scripts that create dozens of nested directories, I realized there’s real depth here: how directory permissions actually get calculated, what a directory even is at the filesystem level, and why -p is one of the most useful flags in all of coreutils. Let me walk through it properly.

What Is the mkdir Command?

mkdir stands for “make directory.” It’s a GNU coreutils utility used to create new directories. Version check from my test environment:

$ mkdir --version
mkdir (GNU coreutils) 9.4
Copyright (C) 2023 Free Software Foundation, Inc.

Basic Syntax

mkdir [OPTION]... DIRECTORY...

What a Directory Actually Is Internally

On Linux, a directory is itself a special kind of file — it has its own inode, but instead of storing arbitrary data, its contents are a list of (name, inode number) pairs mapping the names inside it to their inodes. When mkdir runs, the kernel:

  1. Allocates a new inode for the directory.
  2. Initializes it with a directory-type mode and the requested permissions (adjusted by umask).
  3. Writes two default entries into the new directory’s data: . (pointing to itself) and .. (pointing to its parent).
  4. Adds an entry for the new directory’s name into the parent directory, pointing at the new inode.
  5. Increments the parent directory’s link count, because the .. entry inside the new directory counts as another link to the parent.

You can see this link-count effect directly:

$ mkdir testdir
$ stat testdir | grep Links
Links: 2

A fresh empty directory always starts with a link count of 2 — one for its own name in the parent, one for its internal . self-reference. Adding subdirectories inside it increases that count further, since each subdirectory’s .. entry adds another link back.

Full List of Parameters

OptionLong formDescription
-m--mode=MODESet the permission mode (as in chmod), not affected by umask for explicitly-set bits
-p--parentsCreate parent directories as needed; no error if the directory already exists
-v--verbosePrint a message for each created directory
-ZSet the SELinux security context of each created directory to the default type
--context[=CTX]Like -Z, or specify a context CTX explicitly
--helpDisplay help and exit
--versionOutput version information and exit

Practical Examples with Output

Creating a single directory:

$ mkdir project
$ ls -ld project
drwxr-xr-x 2 root root 4096 Jul 31 01:36 project

Creating nested directories in one shot with -p:

$ mkdir -p a/b/c
$ find a
a
a/b
a/b/c

Without -p, this fails because mkdir refuses to create intermediate directories that don’t exist yet:

$ mkdir x/y/z
mkdir: cannot create directory 'x/y/z': No such file or directory

-p also silently succeeds if the directory already exists, which makes it ideal for idempotent scripts:

$ mkdir -p a/b/c
$ echo $?
0

Running it again produced no error and no output — exactly the behavior you want in a script that might run multiple times.

Creating multiple directories at once:

$ mkdir logs cache tmp
$ ls -d logs cache tmp
cache  logs  tmp

Setting an explicit mode at creation time:

$ mkdir -m 700 private_dir
$ ls -ld private_dir
drwx------ 2 root root 4096 Jul 31 01:36 private_dir

Verbose output while creating nested directories:

$ mkdir -pv app/config/env
mkdir: created directory 'app'
mkdir: created directory 'app/config'
mkdir: created directory 'app/config/env'

How Permissions Are Calculated (umask Interaction)

By default, mkdir requests mode 0777 (full read/write/execute for owner, group, and others), and the kernel then subtracts the bits set in the process’s umask. A typical umask of 022 removes write permission for group and others, resulting in the very common 755 default you see on new directories:

0777
&~ 0022
------
0755

Crucially, when you use -m to specify an explicit mode, GNU mkdir does not apply the umask to the bits you explicitly set — it applies your mode directly (though some implementations still mask intermediate parent directories created along the way when combined with -p, so it’s worth checking with ls -ld after the fact if the exact mode matters for security).

Common Use Cases

  • Setting up a new project’s directory skeleton
  • Creating log, cache, and temp directories during application deployment
  • Building nested directory trees to mirror a data or backup structure
  • Preparing mount points before mounting a filesystem
  • Creating per-user home directory subdirectories during account provisioning

Shell Scripting and Automation

Because -p makes mkdir idempotent, it’s my default choice at the top of nearly every deployment or setup script:

#!/usr/bin/env bash
set -euo pipefail

for d in /var/log/myapp /var/cache/myapp /etc/myapp; do
    mkdir -p "$d"
done

Creating a directory tree with restricted permissions in one line, useful for secrets or private data directories:

mkdir -p -m 700 "$HOME/.ssh"

Combining with chown right after creation, a very common provisioning pattern:

mkdir -p /srv/app/data
chown -R appuser:appgroup /srv/app/data
chmod 750 /srv/app/data

Real-World System Administration Workflows

  • Application deployment: build scripts almost always start with mkdir -p calls to guarantee the target directory structure exists regardless of whether this is a fresh install or a redeploy.
  • Backup systems: scripts creating dated backup directories use mkdir -p "/backups/$(date +%Y/%m/%d)" to build the full year/month/day tree in one call.
  • Container image builds: Dockerfiles frequently run RUN mkdir -p /app/data /app/logs to prepare directories before COPY or VOLUME instructions.
  • User provisioning: automated onboarding scripts create ~/Documents, ~/Downloads, or project-specific directories for new user accounts.

Comparing mkdir to Related Commands

  • mkdir vs install -d: install -d (from coreutils as well) creates directories similarly but is often used in Makefiles because it can also set ownership and mode consistently as part of a broader install step.
  • mkdir -p vs manually checking [ -d dir ] || mkdir dir: functionally equivalent, but -p is simpler and also handles multi-level paths that a single -d check wouldn’t cover.
  • mkdir vs mktemp -d: use mktemp -d when you need a uniquely named, collision-free temporary directory rather than a fixed name you already know.

Troubleshooting Common mkdir Issues

“mkdir: cannot create directory: File exists”: without -p, mkdir errors if the target already exists (even as a file, not just a directory) — add -p for idempotent behavior, or check first if you need to distinguish the two cases.

“mkdir: cannot create directory: Permission denied”: you need write and execute permission on the parent directory to create something inside it — check the parent’s permissions with ls -ld, not the directory you’re trying to create.

“mkdir: cannot create directory: No space left on device”: this can happen even with free disk space if you’ve exhausted inodes — check with df -i in addition to df -h.

Directory created with unexpected permissions: check your shell’s current umask with the umask builtin; a stricter or looser umask than you expect explains most permission surprises.

Performance Considerations

mkdir is essentially instantaneous per call since it only touches metadata, but issuing thousands of individual mkdir calls in a loop (e.g., in a shell for loop over a huge list) is measurably slower than doing it in fewer invocations. Where possible, pass multiple directory arguments to a single mkdir -p call rather than looping with repeated subprocess spawns.

Security Implications

Always be deliberate about the mode you create sensitive directories with. A directory created without -m inherits whatever the umask leaves — often 755, which is world-readable and world-executable (meaning others can list and traverse it, though not modify it). For anything holding secrets, credentials, or private user data, explicitly set a restrictive mode:

mkdir -p -m 700 /etc/myapp/secrets

Also be careful with -p in scripts that build paths from user input — without validation, a malicious or malformed variable could cause directories to be created in unexpected locations. Always validate or sanitize path components derived from external input before passing them to mkdir.

Best Practices

  • Use -p by default in scripts for idempotency.
  • Set an explicit -m mode for directories holding sensitive data rather than relying on umask.
  • Validate any user-supplied path components before using them in mkdir calls.
  • Follow directory creation with chown/chmod when the process creating the directory won’t be the one owning its contents long-term.
  • Group multiple directory creations into a single mkdir call when performance in tight loops matters.

Compatibility Across Distributions

GNU mkdir is uniform across Debian/Ubuntu, RHEL/Fedora, Arch, and openSUSE. BusyBox mkdir (Alpine, many minimal containers) supports -p, -m, and -v but may lack SELinux-related -Z/--context options, which are largely irrelevant outside SELinux-enabled distributions like RHEL/Fedora/CentOS anyway.

Advanced Scenarios I’ve Run Into

Creating directories with a specific ACL inherited from the parent — plain mkdir has no ACL awareness at all, so if the parent directory carries default ACLs (set via setfacl -d), a newly created subdirectory inherits them automatically at the kernel level without mkdir needing to do anything special. It’s worth knowing this inheritance happens transparently, because it explains why a freshly mkdir-ed directory sometimes shows unexpected extra permissions (a trailing + in ls -l) that trace back to the parent’s default ACL rather than anything mkdir itself did.

Race conditions in concurrent scripts: if two processes both check [ -d "$dir" ] || mkdir "$dir" around the same time, there’s a window where both see the directory as missing and both attempt creation — one will fail with “File exists.” Using mkdir -p sidesteps this specific failure mode because -p treats “already exists” as success rather than an error, making the overall pattern safe for concurrent, idempotent use even under a race.

Building a full directory skeleton for a new project in one command:

$ mkdir -pv myproject/{src,tests,docs,scripts}
mkdir: created directory 'myproject'
mkdir: created directory 'myproject/src'
mkdir: created directory 'myproject/tests'
mkdir: created directory 'myproject/docs'
mkdir: created directory 'myproject/scripts'

This combines -p with bash’s brace expansion — the shell expands {src,tests,docs,scripts} into four separate arguments before mkdir ever runs, so this is really the shell doing the heavy lifting, with mkdir -p simply handling each resulting path (including the shared myproject parent) cleanly in one call.

Verifying What Actually Got Created

I’ve developed a habit of following any nontrivial mkdir -p call in a script with a quick sanity check, especially when the path is built from variables:

TARGET="/srv/app/${ENVIRONMENT}/data"
mkdir -p -- "$TARGET"
[[ -d "$TARGET" ]] || { echo "Failed to create $TARGET" >&2; exit 1; }

This costs almost nothing and catches the rare but real case where mkdir -p reports success (exit code 0) but the resulting directory isn’t quite what you assumed — for instance, if $ENVIRONMENT was unexpectedly empty and the path collapsed to something like /srv/app//data, which mkdir -p would still happily create without complaint, potentially not where you intended.

A Note on POSIX vs GNU Behavior

The base mkdir behavior (creating a directory, failing on existing directories or missing parents without -p) is specified by POSIX and consistent everywhere. Where distributions and implementations diverge is in the long-option GNU extensions (--mode, --parents, --verbose, --context) — these are GNU-specific conveniences layered on top of the POSIX baseline. If you’re writing a script meant to run on a strict POSIX system (some BSD variants, certain embedded Linux builds using non-GNU userlands), stick to the short single-letter flags (-p, -m, -v), which are POSIX-specified and portable, rather than the GNU long-option spellings.

Summary

mkdir is simple on the surface but sits directly on top of some fundamental Unix filesystem concepts: directories as special files containing name-to-inode mappings, link counts that grow with nested subdirectories, and permission bits shaped by the interaction between requested mode and umask. Once you understand those mechanics, flags like -p and -m stop being “extra options” and start being the obvious tools for writing safe, idempotent, correctly-permissioned setup scripts.

References

  • GNU Coreutils Manual — mkdir invocation: https://www.gnu.org/software/coreutils/manual/html_node/mkdir-invocation.html
  • Linux man-pages project — mkdir(1): https://man7.org/linux/man-pages/man1/mkdir.1.html
  • mkdir(2) system call documentation: https://man7.org/linux/man-pages/man2/mkdir.2.html
  • Ubuntu Manpage Repository: https://manpages.ubuntu.com/manpages/noble/en/man1/mkdir.1.html
Total
0
Shares

Leave a Reply

Previous Post
ls command in Linux and it perimeters

ls Command in Linux: Complete Guide to Listing Files, Directories, and Parameters

Next Post
mv command in Linux and it perimeters

mv Command in Linux: Complete Guide to Moving and Renaming Files, Directories, and Parameters

Related Posts