mkfs

Make a filesystem on a device or in a file

Updated 2026-09-11

mkfs writes a new, empty filesystem over whatever was there, on a device or in a file that dd made. There is no confirmation step and nothing to undo it, so the argument is worth reading twice: blkid on the device first will say what you are about to destroy.

Like fsck it is a front end that dispatches by type. mkfs -t ext4 runs mkfs.ext4, which is mke2fs under another name, and a type whose helper is not installed fails with a message about the helper rather than about the filesystem. Debian keeps each one in its own package, so mkfs.vfat needs dosfstools and mkfs.xfs needs xfsprogs.

Give -q unless you want to watch it work. The ordinary output reports progress with backspaces and trailing padding, which reads correctly on a terminal and is unusable anywhere else.

Three things are decided at creation and cannot be changed afterwards without remaking the filesystem: the block size, the inode count and the inode size. The defaults come from /etc/mke2fs.conf and suit a general-purpose disk. A filesystem holding a million small files runs out of inodes long before it runs out of space, and df -i is what shows it happening.

The label and the UUID can be changed later with tune2fs, but setting them here is what lets an /etc/fstab entry name the filesystem rather than the device it happened to appear as.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

two disk images blank.img is 16M of zeros with no filesystem on it, and most examples here format that one. data.img holds a 32M ext4 labelled tipsdata with notes.txt and backups/site.tar in it, so the page has something to show being destroyed. Both are rebuilt before every example, so each one starts from the same state whatever the last did. The same pair backs mount, blkid and fsck.

total 20912
-rw-r--r-- 1 root root 16777216 Jun 15 10:00 blank.img
-rw-r--r-- 1 root root 33554432 Jun 15 10:00 data.img
25 outputs, collapsed by default

Making one

-q on every example, because the ordinary output reports progress with backspaces and padding. A successful mkfs prints nothing and says so only through its exit status.

Make an ext4 filesystem

mkfs.ext4 -q /srv/images/blank.img
echo "exit $?"
blkid -c /dev/null -s TYPE -o value /srv/images/blank.img

Nothing to see, which is what success looks like. mkfs.ext4 works on a plain file as readily as on a device, and the result is the same filesystem either way.

Show output
exit 0
ext4

Go through the generic front end

mkfs -t ext4 -q /srv/images/blank.img
blkid -c /dev/null -s TYPE -o value /srv/images/blank.img

mkfs -t ext4 finds and runs mkfs.ext4, passing everything else through. The front end is worth using in a script that takes the type as a variable, and adds nothing when the type is known.

Show output
ext4

Ask for a type whose helper is missing

mkfs -t vfat /srv/images/blank.img; echo "exit $?"

The message is about the helper rather than about the filesystem, and that is the shape of every unsupported type on Debian: mkfs.vfat lives in dosfstools, mkfs.xfs in xfsprogs, and neither is installed by default.

Show output
mkfs: failed to execute mkfs.vfat: No such file or directory
exit 1

Make an older ext filesystem

mkfs.ext3 -q /srv/images/blank.img
blkid -c /dev/null -s TYPE -o value /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^Filesystem features" | tr " " "\n" | grep journal

mkfs.ext2, mkfs.ext3 and mkfs.ext4 are the same binary choosing different defaults. ext3 is ext2 with a journal, which is the feature the last line is looking for.

Show output
ext3
has_journal

Make a filesystem with no journal

mkfs.ext2 -q /srv/images/blank.img
blkid -c /dev/null -s TYPE -o value /srv/images/blank.img

ext2 has no journal, so a crash leaves it needing a full fsck rather than a few seconds of replay. Worth it only where the writes are few and the medium wears out, which in practice means some flash devices.

Show output
ext2

Format a loop device rather than the file

loop=$(losetup --find --show /srv/images/blank.img)
mkfs.ext4 -q "$loop"; echo "exit $?"
blkid -c /dev/null -s TYPE -o value "$loop"

The same result through the device. On real hardware this is the only form, and the argument is /dev/sdb1 rather than /dev/sdb: formatting the whole disk destroys its partition table along with everything on it.

Show output
exit 0
ext4

Mount what you just made

mkfs.ext4 -q -L spare /srv/images/blank.img
loop=$(losetup --find --show /srv/images/blank.img)
mount "$loop" /mnt/backup
df -h /mnt/backup | tail -1

14M usable out of 16M: the journal, the inode tables and the reserved blocks are all spent before a single file is written. The smaller the filesystem, the larger that share.

Show output

Your output will differ: the loop device number depends on what else on this machine has one attached

/dev/loop0       14M   46K   13M   1% /mnt/backup

Naming it

A label and a UUID are both written at creation, and both can be changed later with tune2fs. Naming the filesystem lets /etc/fstab refer to it without naming a device that may renumber.

Give the filesystem a label

mkfs.ext4 -q -L spare /srv/images/blank.img
blkid -c /dev/null -s LABEL -s TYPE /srv/images/blank.img
echo "uuid characters: $(blkid -c /dev/null -o value -s UUID /srv/images/blank.img | tr -d '\n' | wc -c)"

-L sets the label, and a UUID is generated whether you ask or not: 36 characters of one, different on every run, which is why the count stands in for it here. -c /dev/null stops blkid answering from a cache that still holds the reading from before the format.

Show output
/srv/images/blank.img: LABEL="spare" TYPE="ext4"
uuid characters: 36

Set the UUID as well

mkfs.ext4 -q -L spare -U 22222222-3333-4444-5555-666666666666 /srv/images/blank.img
blkid -c /dev/null /srv/images/blank.img

-U pins the identifier instead of taking a random one. Worth doing when a machine is being rebuilt to a known configuration, so the /etc/fstab written for it stays right.

Show output
/srv/images/blank.img: LABEL="spare" UUID="22222222-3333-4444-5555-666666666666" BLOCK_SIZE="1024" TYPE="ext4"

Give a label that is too long

mkfs.ext4 -q -L "this-label-is-far-too-long-for-ext4" /srv/images/blank.img; echo "exit $?"

ext4 stores sixteen characters and truncates the rest, with a warning rather than a failure. A script that later looks the label up by its full name finds nothing.

Show output
Warning: label too long; will be truncated to 'this-label-is-fa'

exit 0

Choosing the shape

Block size, inode count and inode size are fixed at creation. dumpe2fs -h reads them back, and the defaults come from /etc/mke2fs.conf rather than from anything on the command line.

See what the defaults produced

dumpe2fs -h /srv/images/data.img 2>/dev/null | grep -E "^(Block size|Inode count|Block count|Reserved block count|Inode size)"

The numbers a plain mkfs.ext4 chose for a 32M image. dumpe2fs sends its banner to standard error, which is what the redirect drops.

Show output
Inode count:              8192
Block count:              32768
Reserved block count:     1638
Block size:               1024
Inode size:               256

Read Debian's defaults for yourself

grep -A6 "^\[defaults\]" /etc/mke2fs.conf

/etc/mke2fs.conf is where the numbers come from before any flag adjusts them. enable_periodic_fsck = 0 is the line that stops Debian checking a filesystem every so many mounts.

Show output
[defaults]
	base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
	default_mntopts = acl,user_xattr
	enable_periodic_fsck = 0
	blocksize = 4096
	inode_size = 256
	inode_ratio = 16384

Set the block size

mkfs.ext4 -q -b 4096 /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^(Block size|Block count)"

The block size is four times larger, so there are a quarter as many of them. A large block size wastes space on small files and moves large ones faster, and it cannot be changed afterwards.

Show output
Block count:              4096
Block size:               4096

Ask for a fixed number of inodes

mkfs.ext4 -q -N 256 /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^(Inode count|Block count|Free inodes)"

-N names the count outright. Twelve are spent before anything is written, on the root directory, lost+found and the reserved inodes ext4 keeps for itself.

Show output
Inode count:              256
Block count:              16384
Free inodes:              244

Set the ratio instead of the count

mkfs.ext4 -q -i 8192 /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^Inode count"

-i is bytes of filesystem per inode, so a smaller number buys more of them. Give it on a filesystem that will hold mail or a package cache, where the files are many and tiny.

Show output
Inode count:              2048

Use a named usage type

mkfs.ext4 -q -T small /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^(Block size|Inode count)"

-T picks a set of defaults from /etc/mke2fs.conf by name: small, floppy, big, largefile. Easier to get right than the individual flags, and easier to explain to whoever reads the script next.

Show output
Inode count:              4096
Block size:               1024

Reclaim the reserved blocks

mkfs.ext4 -q -m 0 /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^Reserved block count"

The default holds five per cent back for root, so a full filesystem still lets root log in and a daemon writing as itself stops first. Setting it to 0 is right for a data disk and wrong for anything the system boots from.

Show output
Reserved block count:     0

Turn a feature off

mkfs.ext4 -q -O "^has_journal" /srv/images/blank.img
dumpe2fs -h /srv/images/blank.img 2>/dev/null | grep -E "^Filesystem features"

A ^ in front of a feature name removes it, so this is an ext4 without a journal. The quoting matters: unquoted, the shell reads ^ as an ordinary character here but a different shell may not.

Show output
Filesystem features:      ext_attr resize_inode dir_index filetype extent 64bit flex_bg metadata_csum_seed sparse_super large_file huge_file dir_nlink extra_isize metadata_csum

Own the root directory as someone else

mkfs.ext4 -q -E root_owner=1000:1000 /srv/images/blank.img
loop=$(losetup --find --show /srv/images/blank.img)
mount "$loop" /mnt/backup
stat -c "%U:%G %a" /mnt/backup

A new filesystem's root belongs to root at mode 755, so a user cannot write to it until someone runs chown. -E root_owner settles it at creation, which matters when the filesystem is built by a script that will not be around later.

Show output
user:user 755

Before you press return

mkfs destroys what it is pointed at without asking. It refuses only one thing, and everything else is on you.

See what it would make without making it

mkfs.ext4 -n -U 22222222-3333-4444-5555-666666666666 /srv/images/blank.img

-n prints the parameters and writes nothing, which is the check to run against a device you are not certain about. It still needs -U to be worth comparing, since the UUID it would use is otherwise made up fresh each time.

Show output
mke2fs 1.47.2 (1-Jan-2025)
Creating filesystem with 16384 1k blocks and 4096 inodes
Filesystem UUID: 22222222-3333-4444-5555-666666666666
Superblock backups stored on blocks:
	8193

Try to format a mounted filesystem

loop=$(losetup --find --show /srv/images/data.img)
mount "$loop" /mnt/data
mkfs.ext4 -q "$loop"; echo "exit $?"

The one refusal mkfs makes on its own. Unmount and it will do exactly what you asked, so this catches the mistake rather than the intent.

Show output

Your output will differ: the loop device number depends on what else on this machine has one attached

/dev/loop0 is mounted; will not make a filesystem here!
exit 1

Format over a filesystem with files on it

mkfs.ext4 -q -L data /srv/images/data.img
blkid -c /dev/null -s LABEL -s TYPE /srv/images/data.img
loop=$(losetup --find --show /srv/images/data.img)
mount "$loop" /mnt/data
ls -A /mnt/data

data.img held notes.txt and a backups directory, and now holds the empty directory mkfs makes. It was unmounted, so nothing objected. Run blkid on the target first: a label you recognise is the cheapest check there is.

Show output
/srv/images/data.img: LABEL="data" TYPE="ext4"
lost+found

Fill the filesystem as you make it

mkdir -p /tmp/seed/docs
echo hello > /tmp/seed/notes.txt
echo doc > /tmp/seed/docs/a.txt
mkfs.ext4 -q -d /tmp/seed /srv/images/blank.img
loop=$(losetup --find --show /srv/images/blank.img)
mount "$loop" /mnt/backup
ls -R /mnt/backup

-d copies a directory tree into the filesystem while building it, with no mount involved. This is how a disk image is populated in a build script, and it needs no privilege beyond what making the filesystem already took.

Show output
/mnt/backup:
docs
lost+found
notes.txt

/mnt/backup/docs:
a.txt

/mnt/backup/lost+found:

Confirm the filesystem really is a new one

blkid -c /dev/null -o value -s UUID /srv/images/data.img > /tmp/before
mkfs.ext4 -q -L data /srv/images/data.img
blkid -c /dev/null -o value -s UUID /srv/images/data.img > /tmp/after
if cmp -s /tmp/before /tmp/after; then echo "same uuid"; else echo "new uuid"; fi

A fresh UUID every time, which is what breaks an /etc/fstab entry naming the old one. Reformatting a disk and leaving fstab alone is a reliable way to make a machine drop to an emergency shell at its next boot.

Show output
new uuid

Format the same image twice

mkfs.ext4 -q /srv/images/blank.img
mkfs.ext4 -q /srv/images/blank.img
echo "exit $?"

No warning and no prompt: the second one overwrites the first without noticing it was there. A device already carrying a filesystem gets a confirmation only when mkfs is talking to a terminal, which a script never is.

Show output
exit 0