Inode: Explanation & Insights
A data structure storing a file's metadata and disk location on a Linux filesystem, identified by number rather than by filename.
What It Is
An inode is the small on-disk record that is a file. Not the name, not the contents you cat — the inode itself: a fixed-size structure that holds everything the kernel needs to know about a file except the one thing everybody assumes lives there. It stores the file's size, its owner and group, its permission bits, its timestamps, its link count, and a set of pointers to the block device blocks where the actual bytes live. What it does not store is the filename. Sit with that for a moment, because it's the single most clarifying idea in all of Unix, and the whole rest of this page unfolds from it.
Here's the mental model to lock in now and never let go: the file is the inode; the name is just a directory entry pointing at it. A directory isn't a container that holds files the way a folder holds paper. A directory is a tiny table — a list of pairs, each one a human-readable name next to an inode number. That's all. When you ls /etc, you are reading that table. When you open /etc/hostname, the filesystem looks up the name hostname in the /etc directory table, finds the inode number sitting beside it, jumps to that inode, reads its block pointers, and fetches the data. The name was never the file. The name was a label on a drawer, and the inode number told you which drawer.
Every file and every directory on a Linux filesystem — every regular file, every directory, every symlink, every device node — has exactly one inode, identified by a number that's unique within that filesystem. That number is the file's true name, the one the system actually uses. The human-readable name is a convenience layered on top, and as we'll see, a file can have several of them, or — briefly, strangely — none at all while still being very much alive.
This page goes deep. We'll pull apart what an inode actually contains, show you how to read it with ls, stat and df, and use the name-versus-inode split to demystify four things that confuse almost everyone the first time: why hard links aren't copies, why rm is really called unlink, why a deleted file can keep eating your disk, and why a filesystem with gigabytes free can still refuse to create a single new file.
Backstory
The "i" almost certainly stands for index — the inode is an entry in the filesystem's index of files, the array you reach into by number. Ken Thompson, who designed the structure for the original Unix, said in later interviews he no longer remembered exactly why he picked the letter, but "index node" is the reading that has always made the most sense, and "inode" is how the world spells it now. A small, humble word for the thing that turns out to be the file itself.
Why It Matters
Once the inode clicks, half of Unix stops being magic and starts being obvious. Hard links, the behaviour of rm, the reason mv within a disk is instant while mv across disks crawls, the infamous "I deleted the log file but the disk is still full" — none of these are quirks to memorise. They're all the same idea seen from different angles: names and files are separate things, joined by a number and a counter.
It matters operationally too, in a way that bites people who've run servers for years. Inodes are a finite resource, separate from disk space, and you can run out of them while df still shows plenty of room. A mail server's maildir, a PHP session directory, a build cache, a Git repo with a million loose objects — anything that breeds tiny files — can exhaust the inode table and hand your application a "No space left on device" error with the disk half empty. If you don't know inodes exist, that error is baffling. If you do, it's a five-second diagnosis. We'll cover both the diagnosis and the fix-at-format-time, because the real fix happens before the problem ever appears.
What an Inode Holds
The inode is a fixed-size record (256 bytes on a modern ext4 filesystem) packed with metadata. Here's the field-by-field tour — the obscure ones included, because someday you'll want them.
- File type and mode. A single field encodes both what kind of object this is (regular file, directory, symlink, block/character device, FIFO, socket) and its permission bits — the
rwxr-xr-xyou see inls -l. The leading character inls -loutput (-,d,l,b,c,p,s) comes straight from this field. - Owner and group. The numeric UID and GID. Names like
rootorwww-dataare looked up from/etc/passwdand/etc/groupat display time — the inode only ever stores numbers. - Link count. How many directory entries (hard links) point at this inode. This little counter is the hero of half this page. When it hits zero and no process holds the file open, the data blocks are freed.
- Size. The file's length in bytes.
- Timestamps. Three of them, and people mix them up constantly: atime (last access — last read), mtime (last modification — contents last changed), and ctime (last inode change — when metadata like permissions or the link count last changed). Note there is no "creation time" in classic Unix inodes; ext4 added a fourth, crtime (birth time), but most tools still don't show it. The
ctimeis the one everyone misreads as "creation time" — it is not. - Block pointers. The map from the file to its actual data on disk. Classic Unix used a clever scheme of direct, single-, double-, and triple-indirect pointers; modern filesystems like ext4 and XFS use extents instead ("blocks 5000–5099 are mine"), which is far more compact for big files. Either way, this is where the inode says where the bytes live.
Notice what's missing from that whole list. No name. The inode is the file stripped of its label — pure identity and pure metadata, with the contents one pointer-hop away.
How I Inspect It
You read inodes with three tools, and you reach for them in a predictable order: ls -i when you just want the number, stat when you want the whole record, and df -i when you want to know if you're about to run out.
ls -i — The Number Beside the Name
The fastest way to see an inode number is ls with -i:
ls -li /etc/hostname
131079 -rw-r--r-- 1 root root 12 May 3 09:14 /etc/hostname
That leading 131079 is the inode number. The column right after the permissions — the 1 — is the link count: exactly one name points at this inode. Keep an eye on that number; it's about to tell a story.
stat — The Whole Record
When you want the entire inode laid out, stat is the tool. It does nothing but read the inode and print it in full:
stat /etc/hostname
File: /etc/hostname
Size: 12 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 131079 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2026-06-14 09:31:02.000000000 +0200
Modify: 2026-05-03 09:14:55.000000000 +0200
Change: 2026-05-03 09:14:55.000000000 +0200
Birth: 2026-05-03 09:14:55.000000000 +0200
Every field we discussed above is right there: the inode number, the link count (Links: 1), owner, group, size, the three-or-four timestamps. stat is the closest you get to looking the inode in the eye. If you ever wonder "what does the system actually know about this file," this is the answer.
df -i — Counting the Inodes Themselves
This is the one that saves the day. Ordinary df shows you blocks — space. Add -i and it shows you inodes instead — how many files you can still create:
df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda2 15278080 214883 15063197 2% /
/dev/sdb1 6553600 6553600 0 100% /data
Look at /data: IUse% is 100%. Every inode is spoken for, IFree is 0, and that filesystem cannot create one more file no matter how much block space df says is free. We'll return to this in the out-of-inodes section — but the muscle memory to build is: any "No space left on device" that doesn't match what plain df shows, run df -i immediately.
The Big Idea: Name and File Are Separate Things
Everything that follows is the same reframe applied four ways. Internalise it once and you'll re-derive each consequence on the spot for the rest of your career.
Hard Links: Two Names, One File
A hard link is a second directory entry pointing at an inode that already has one. Not a copy — the same inode, the same data blocks, the same everything. You make one with ln (no -s):
ln /var/log/app.log /var/log/app-backup.log
Now watch the link count move:
ls -li /var/log/app.log /var/log/app-backup.log
262145 -rw-r--r-- 2 root root 4096 Jun 14 09:40 /var/log/app.log
262145 -rw-r--r-- 2 root root 4096 Jun 14 09:40 /var/log/app-backup.log
Same inode number, 262145, on both lines — proof they're literally the same file. And the link count is now 2: two names, one inode. Edit one, the other changes, because there is no "other"; there is one file with two labels. Neither name is the "original" and neither is the "link" — the filesystem has no idea which came first, and frankly doesn't care. They are perfectly equal entries pointing at the same inode. That symmetry is the tell that a hard link is something deeper than a shortcut.
This is also exactly why you can't hard-link across filesystems. A hard link is just a name paired with an inode number, and inode numbers are only unique within a single filesystem. The number 262145 on /dev/sda2 and the number 262145 on /dev/sdb1 are two completely unrelated files. A directory entry on one filesystem has no way to address an inode living on another — the number would be meaningless over there. So ln across a mount boundary fails with Invalid cross-device link, and now you know it's not a restriction someone bolted on — it's geometry. (A symlink can cross filesystems, precisely because it stores a text path rather than an inode number; that's the fundamental difference between the two kinds of link.)
And it's why renaming or moving a file within one filesystem is instant, no matter how huge the file. mv hugefile.iso elsewhere/ on the same filesystem doesn't touch a single data block — it deletes one directory entry and writes another, both pointing at the same untouched inode. Move it to a different filesystem and mv has to genuinely copy every byte and then delete the source, because the inode can't follow. Same command, wildly different cost, and the inode model tells you which you're about to pay before you press Enter.
Why rm Is Really unlink
Here's a fact that rearranges how you think about deletion: there is no rm system call. When rm removes a file, the syscall it actually makes is unlink(). It doesn't "delete the file" — it removes one name, one directory entry, and decrements that inode's link count by one.
If the count is still above zero afterward (other hard links remain), nothing else happens — the file is entirely intact under its other names. Only when the link count drops to zero does the filesystem consider freeing the inode and returning its data blocks to the pool. "Deleting a file" is, mechanically, "removing the last name and watching the counter hit zero." The whole operation is a decrement.
Note
This is why
rmdoesn't get slower on bigger files. Removing a 1 KB file and removing a 50 GB file are the same operation — unlink the name, decrement the count, mark the blocks free. The blocks aren't scrubbed or zeroed; they're just flagged available. (Which, as a free aside, is exactly why undelete tools and forensic recovery work: the bytes sit there untouched until something else overwrites them.)
The Famous Gotcha: Deleted, But Still Eating Disk
Now the payoff — the classic that has stumped admins for forty years. The link count hitting zero is only half the condition for freeing the data. The full rule is: an inode's blocks are released when its link count is zero and no running process still holds the file open.
So picture this. A service is logging to /var/log/app.log. The disk fills, and you do the obvious thing — rm /var/log/app.log. The link count goes to zero, the name vanishes from the directory, ls no longer shows it. You check df, expecting relief... and the space is still gone. Nothing freed.
Why? The service still has that file open. The directory entry is gone, but the process holds an open file descriptor, which is its own reference to the inode. The kernel keeps an internal handle count alongside the on-disk link count, and it will not release a single block while either is non-zero. So you've got a ghost: a file with no name, invisible to ls, still being written to, still holding gigabytes hostage. Restart the service (or signal it to reopen its logs) and the descriptor closes, the last reference drops, and now the space comes back — instantly.
Warning
When
dfshows a disk full butduon the same path adds up to far less, suspect a deleted-but-open file. Find the culprit withlsof | grep deleted— it lists every open file whose name is already gone, and the PID holding it. The fix is almost always to restart (orHUP) the process that owns the descriptor; deleting "harder" does nothing, because there's no name left to delete. The file is waiting on a handle, not on you.
This is the inode model in its purest form. A file with no name is a perfectly valid file as long as something still refers to it — a directory entry or an open descriptor. The name was never the file. It never was.
Out of Inodes: Full With Space to Spare
The other inode surprise is the mirror image of disk-full, and it catches people who have never once thought about inodes as a countable thing.
On ext4, the number of inodes is fixed when the filesystem is created and can never grow afterward. mkfs.ext4 lays down one inode per 16 KB of space by default (the bytes-per-inode ratio). That default assumes files averaging tens of kilobytes — a perfectly reasonable bet for a general-purpose system. But the bet is fixed in concrete at format time, and some workloads break it spectacularly.
Consider a filesystem full of tiny files: a mail server's maildir with millions of one-kilobyte messages, a web app spilling session files, a cache of small thumbnails, an npm/node_modules tree, a Git object store. Each file — however small — consumes one inode. Fill the inode table and the filesystem reports No space left on device on the very next touch, create, or open(O_CREAT), while plain df cheerfully reports gigabytes free. The application sees a full disk. The admin sees free space. They are both right, looking at different ledgers.
The diagnosis is one command — df -i — and an IUse% of 100% is the smoking gun. The full walkthrough lives on the out-of-inodes page; what matters here is the cure, because the real cure isn't at diagnosis time.
Pro Tip
If you know a filesystem will hold a sea of tiny files — a maildir spool, a session store, a small-object cache — bump the inode density at format time, before you ever put data on it.
mkfs.ext4 -i 4096 /dev/sdb1gives you one inode per 4 KB instead of the default 16 KB, roughly quadrupling the inode count. Even more directly,mkfs.ext4 -N <count>sets the exact number. You can't add inodes to an ext4 filesystem after the fact — the only retrofit is back up, reformat with more inodes, restore — so spend the thirty seconds of foresight now. (And if you genuinely can't predict the file-size profile, this is one of the strongest arguments for XFS, which allocates inodes dynamically and simply never has this conversation.)
That last point is worth saying plainly, because a term page should give direction: for a workload you know is small-file-heavy, either format ext4 with extra inodes up front, or use XFS. Don't accept the default ratio and hope. The default is tuned for the average server, and a maildir is not the average server.
Cheat Sheet
# --- See the inode number ---
ls -i file # inode number beside the name
ls -li # long listing: inode, link count, the lot
stat file # the full inode record, every field
# --- Inode capacity (the "full but not full" check) ---
df -i # inodes used/free per filesystem
df -i /data # just the one mount
# --- Hard links (same inode, link count goes up) ---
ln source newname # create a hard link (no -s)
ls -li source newname # confirm: identical inode number
stat -c '%h' file # print just the link count
# --- Find files by inode / find hard links ---
find . -inum 262145 # every name pointing at this inode
find . -samefile source # all hard links to 'source'
# --- The deleted-but-open gotcha ---
lsof | grep deleted # files unlinked but still held open
lsof +L1 # files with link count < 1 (same idea, cleaner)
# --- Format with more inodes (do this BEFORE you fill it) ---
mkfs.ext4 -i 4096 /dev/sdb1 # one inode per 4 KB (4x the default density)
mkfs.ext4 -N 10000000 /dev/sdb1 # an exact inode count
tune2fs -l /dev/sdb1 | grep -i inode # read the inode settings of an existing fs
Pro Tip
find . -inum Nandfind . -samefile pathare how you hunt down every hard link to a file. Ifstatshows a link count of 5 and you only know about one name, those two commands find the other four — invaluable before you "delete" something and discover it's still very much alive under names you forgot about.
History and Philosophy
The inode dates to the very first Unix, written by Ken Thompson and Dennis Ritchie at Bell Labs around 1969–1971, and it has barely changed in concept since — which tells you how right they got it on the first try. Their decision to separate the file (the inode) from its name (the directory entry) was not an obvious one; plenty of contemporary systems welded the name and the data together into a single object. Thompson and Ritchie split them, and that split is the quiet foundation under an enormous amount of what makes Unix pleasant: hard links, atomic renames, the ability to delete a file out from under a running program without the program crashing, and the elegant uniformity that "everything is a file" — devices, pipes, sockets — all gets an inode and plays by the same rules.
The original block-pointer scheme was itself a small marvel of pragmatic engineering. An inode held a handful of direct pointers (enough for a small file with zero overhead), then a single-indirect pointer (a block full of pointers), a double-indirect, and a triple-indirect — a structure that kept tiny files cheap while still addressing enormous ones. Modern filesystems traded that for extents, but the philosophy is unchanged: the inode is the fixed, numbered anchor, and everything about the file hangs off it.
There's a lovely consequence buried in the design that's worth ending on. Because a file is its inode and names are just pointers, a Unix file has no single canonical name — it has however many names point at it, zero included. A program can open() a file, unlink() every name it has, and keep happily reading and writing a file that, from the directory tree's point of view, no longer exists. It's a file that has been erased from the map but not from the territory. Most operating systems would call that a contradiction. Unix calls it Tuesday — and it's exactly the kind of quiet, principled weirdness that rewards anyone who takes the time to understand what an inode really is.
See Also
- filesystem — the structure that organises inodes, blocks, and directories on a disk
- directory — the name-to-inode table that gives files their human-readable labels
- hard-link — a second name for the same inode, link count and all
- symlink — the other kind of link: a stored path, not an inode number, so it crosses filesystems
- permission — the mode bits the inode carries, and how to read them
- out-of-inodes — full inode table, free disk space, baffled application
- disk full — the other storage emergency, about blocks rather than inodes
- ext4 — the default Linux filesystem, where inode count is fixed at format time
df— check both block usage and, with-i, inode usagels—-ishows the inode number,-lishows the link count toostat— print an inode's full record, every fieldmkfs.ext4— where you set inode density, before the filesystem exists
Hit "No space left on device" with the disk showing space to spare?
CleverUptime watches every mounted filesystem and flags the ones creeping toward full — on blocks or on inodes — in plain language, before an application starts failing to create files.
Want to see your own server's health right now? One command, no signup, no install.