mkfs
Make a filesystem on a device or in a file
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
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