Partitioning a disk feels like a bigger deal than it actually is the first few times you do it, mostly because the consequences of getting it wrong are so unforgiving. fdisk was the first partitioning tool I ever learned, back when MBR was still the default everywhere, and it’s still the tool I reach for today even though it’s grown to support GPT just as well. Here’s the full picture — syntax, internals, real tested output, and the workflow I actually use.
What fdisk Does
fdisk creates, deletes, resizes, and inspects partition tables on block devices. A partition table is metadata stored at the start of a disk describing how the disk’s address space is divided into partitions, each of which can later hold its own independent filesystem. fdisk doesn’t touch filesystem content at all — it operates one layer below mkfs, purely on the addressing structure.
Since util-linux 2.26, fdisk supports both legacy MBR (DOS) partition tables and modern GPT (GUID Partition Table) tables, auto-detecting or letting you create either type.
Syntax
fdisk [options] <disk>
fdisk [options] -l [<disk> ...]
Key Command-Line Options
| Option | Description |
|---|---|
-l | List partition tables for the specified device(s), or all detected devices if none given |
-u | Display sizes in sectors instead of cylinders (default behavior on modern versions) |
-b SECTOR-SIZE | Specify sector size when it can’t be auto-detected (rare, mostly for disk images) |
-c | Compatibility mode toggle for DOS |
-w always|auto|never | Whether to wipe old filesystem/RAID signatures on the device before writing |
-x | Show extra information in expert mode |
-C, -H, -S | Manually specify cylinders, heads, sectors per track (legacy geometry override) |
Tested Example: fdisk -l
I ran this against real devices in a live container:
$ fdisk -l
Disk /dev/loop0: 50 MiB, 52428800 bytes, 102400 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/vda: 256 GiB, 274877906944 bytes, 536870912 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/vdb: 9.69 MiB, 10158080 bytes, 19840 sectors
Units: sectors of 1 * 512 = 512 bytes
This gives disk geometry for every attached block device — total capacity, sector count, and logical/physical sector sizes, useful for confirming whether a disk uses 512-byte or 4K native sectors (4Kn drives).
Interactive Session Walkthrough
Running fdisk /dev/sdb (no -l) drops you into an interactive menu-driven session. I tested this end-to-end on a loop device:
$ fdisk /dev/loop0
Welcome to fdisk (util-linux 2.39.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Device does not contain a recognized partition table.
Created a new DOS (MBR) disklabel with disk identifier 0xb0da6495.
Command (m for help):
From here, the common commands I use most:
| Key | Action |
|---|---|
m | Show the help menu of all available commands |
p | Print the current partition table |
n | Create a new partition |
d | Delete a partition |
t | Change a partition’s type ID |
g | Create a new empty GPT partition table |
o | Create a new empty DOS (MBR) partition table |
w | Write changes to disk and exit |
q | Quit without saving |
v | Verify the partition table for errors |
l | List all known partition type codes |
F | List free/unpartitioned space |
Creating a new primary partition interactively:
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-204799, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-204799, default 204799): +50M
Created a new partition 1 of type 'Linux' and of size 50 MiB.
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
I confirmed the result with fdisk -l on the resulting device:
Device Boot Start End Sectors Size Id Type
/dev/loop0p1 2048 104447 102400 50M 83 Linux
Non-Interactive/Scripted Usage
fdisk supports scripted operation by piping a command sequence to it, which is what makes it usable in automation despite its interactive menu design:
echo -e "n\np\n1\n\n\nw" | fdisk /dev/sdb
That sequence: n (new partition), p (primary), 1 (partition number), blank line twice (accept default first/last sector, spanning the whole disk), w (write). I use this pattern constantly in cloud-init and Packer build scripts where a full interactive session isn’t possible.
For anything more complex than a single full-disk partition, I switch to sfdisk (fdisk’s script-oriented sibling) instead, since chaining raw keystrokes for multi-partition layouts gets fragile fast:
echo '2048,+2G,83
,,83' | sfdisk /dev/sdb
MBR vs GPT
This is the decision point every partitioning session starts with:
| Aspect | MBR (DOS) | GPT |
|---|---|---|
| Max disk size | 2 TiB | 8+ ZiB (practically unlimited) |
| Max primary partitions | 4 (or 3 + 1 extended for more) | 128 by default |
| Boot firmware | BIOS/legacy | UEFI (though GPT can boot BIOS via boot loader tricks) |
| Redundancy | Single partition table copy, fragile | Primary + backup table at end of disk, plus CRC checksums |
| Partition identification | Numeric type byte (e.g., 83 = Linux) | GUID type + partition name string |
I default to GPT for anything larger than 2 TB or any UEFI-booting system — which today is nearly everything — and only reach for MBR on legacy BIOS systems or very old embedded targets that explicitly require it.
Switching table types inside fdisk:
Command (m for help): g
Created a new GPT disklabel (GUID: ...)
How fdisk Works Internally
The partition table itself lives in the first sector(s) of the disk. For MBR, that’s a fixed 512-byte structure at sector 0: a 446-byte boot code area, four 16-byte partition entries, and a 2-byte boot signature (0x55AA). Each partition entry records a type byte, starting/ending CHS or LBA address, and partition size in sectors — that’s the entire structure, which is why MBR is limited to four primary entries.
GPT is more elaborate: a protective MBR at sector 0 (for backward compatibility so old tools don’t misinterpret the disk as unpartitioned), followed by a primary GPT header at sector 1 containing a CRC32 checksum of itself and of the partition entry array, then the partition entry array itself (typically 128 entries of 128 bytes each), and a mirrored backup copy of both the header and entry array at the very end of the disk. This redundancy is why GPT recovers gracefully from a corrupted primary table — gdisk/fdisk can rebuild the primary from the verified backup.
When you press w in fdisk, it writes the updated table to disk and then issues the BLKRRPART ioctl (or on kernels/situations where the kernel can’t re-read a table for a currently-in-use disk, it warns you a reboot or partprobe is required) so the kernel re-enumerates partition device nodes like /dev/sdb1 without needing a reboot.
Real-World Workflow: Adding a New Data Disk
# 1. Confirm the new disk is visible and identify it correctly
lsblk
fdisk -l /dev/sdc
# 2. Create a GPT table and a single full-disk partition
fdisk /dev/sdc
# g (new GPT), n (new partition, defaults for full disk), w (write)
# 3. Confirm the kernel picked up the new partition
partprobe /dev/sdc
lsblk /dev/sdc
# 4. Format and mount as usual
mkfs.ext4 -L data /dev/sdc1
mkdir -p /mnt/data
mount /dev/sdc1 /mnt/data
I always run lsblk before touching anything with fdisk — confirming the exact device name against expected size is the single habit that has saved me from partitioning the wrong disk more than once.
Troubleshooting
“Device or resource busy” on write — the disk or one of its partitions is currently mounted or held by the kernel (e.g., active LVM/mdadm member); unmount and deactivate first, or reboot to apply changes cleanly.
Kernel doesn’t see new partition after w — run partprobe <device> or partx -a <device> to force a re-read without rebooting; on a genuinely busy device, a reboot may still be required.
“GPT PMBR size mismatch” warning — usually appears after resizing a virtual disk (common in VMs/cloud images) where the protective MBR still reflects the old size; fdisk‘s x expert menu offers a “fix GPT data structures” repair, or growpart/gdisk can be used to correct it.
Accidentally deleted the wrong partition — as long as you haven’t pressed w, fdisk changes are only in memory; quit with q to discard. If you already wrote, tools like testdisk can often reconstruct a lost partition table from filesystem superblock scans — but this is a recovery scenario, not routine use.
Comparison with Related Tools
| Tool | Notes |
|---|---|
fdisk | Interactive, supports both MBR and GPT, most widely available |
gdisk | GPT-specific counterpart with more GPT-native terminology and repair tools |
parted | Scriptable from the command line directly (no piped keystrokes needed), also handles filesystem-aware resizing in some cases |
sfdisk | Script-first partitioning, ideal for reproducible, version-controlled disk layouts |
lsblk | Read-only inspection of block devices and their partitions/mountpoints, pairs well before/after any fdisk session |
I reach for parted --script or sfdisk over raw piped fdisk keystrokes whenever a layout needs to be reproducible in automation, and keep interactive fdisk for one-off manual work where I want to see and confirm each step.
Security Implications
Partition tables are metadata, not access-controlled data in the usual sense — anyone with write access to the raw block device (/dev/sdX, requiring root or membership in the disk group) can rewrite them freely, instantly destroying the addressing information needed to locate existing filesystems (the underlying filesystem data usually survives until overwritten, which is exactly why partition-recovery tools work, but the system won’t know how to find it without the table). Never grant disk group membership or raw block device access to an account that doesn’t need it — it’s functionally equivalent to full root-level data access on that disk.
Distribution Compatibility
fdisk ships as part of util-linux, present by default on essentially every Linux distribution: Debian, Ubuntu, Fedora, RHEL/CentOS/Rocky/Alma, Arch, openSUSE. A small number of minimal container base images (I confirmed this directly on a stripped-down Ubuntu container image) omit the fdisk package specifically even though the rest of util-linux is present, requiring apt install fdisk — worth checking with which fdisk before assuming it’s there in a minimal or containerized environment.
Partition Type Codes and Their Meaning
Every MBR partition entry carries a one-byte type ID that historically hinted at what filesystem or purpose it served — a convention that predates modern auto-detection but is still written and respected today. A few of the ones I encounter most often, viewable interactively with l inside fdisk:
| Code | Meaning |
|---|---|
83 | Linux (the generic catch-all for ext2/3/4, XFS, Btrfs — the code doesn’t distinguish between them) |
82 | Linux swap |
8e | Linux LVM physical volume |
fd | Linux RAID autodetect (legacy mdadm marker) |
ef | EFI System Partition |
07 | NTFS/exFAT (shared code with some other Windows formats) |
0c | FAT32 (LBA-addressed) |
On GPT disks, the equivalent concept uses full GUIDs rather than single bytes — for instance, 0FC63DAF-8483-4772-8E79-3D69D8477DE4 specifically identifies a “Linux filesystem data” partition, and C12A7328-F81F-11D2-BA4B-00A0C93EC93B identifies an EFI System Partition. fdisk‘s t command lets you set these interactively by short mnemonic rather than requiring you to type the full GUID by hand.
Verifying a Partition Table Without Modifying It
Before making any changes, I always run a read-only pass first:
fdisk -l /dev/sdb
And inside an interactive session, the p (print) and v (verify) commands let you inspect the current state and check for internal consistency issues — overlapping partitions, partitions extending beyond the disk’s addressable range — without writing anything, since nothing is committed until w is explicitly issued. This “look before you write” discipline is the single habit that separates routine partitioning work from data-loss incidents, and it costs nothing extra to practice consistently.
Handling Disks Already in Use by LVM or mdadm
A disk that’s already a member of an LVM volume group or an mdadm RAID array will often resist fdisk changes with “device busy” errors, precisely because the kernel is actively holding it open through that layer. The correct sequence is to tear down the higher layer first — vgremove/pvremove for LVM, or mdadm --stop/mdadm --zero-superblock for RAID — before fdisk can safely repartition the underlying device. Skipping this step and forcing a repartition while the layer above is still active is a reliable way to corrupt both the partition table and whatever LVM/RAID metadata was relying on the old layout.
Summary
fdisk sits at the foundation of disk management on Linux: before a filesystem, before a mount point, there has to be a partition table describing how the disk’s raw space is carved up, and fdisk is still the most direct, most universally available tool for building and inspecting that structure, whether you’re working with legacy MBR or modern GPT. The interactive menu takes a few sessions to feel natural, but the underlying model — geometry, partition entries, table types — carries over cleanly to every other partitioning tool you’ll ever touch.
References
- util-linux —
fdisk(8): https://man7.org/linux/man-pages/man8/fdisk.8.html - util-linux —
sfdisk(8): https://man7.org/linux/man-pages/man8/sfdisk.8.html - UEFI Forum — GPT Specification: https://uefi.org/specifications
- Red Hat Documentation — Managing Storage Devices: https://access.redhat.com/documentation/
- Arch Wiki — Partitioning: https://wiki.archlinux.org/title/Partitioning
