Basic overview on inodes
This short article aims to bring a surface level overview on inodes.
What is an inode?
An inode is short for index node, which belongs to a type of data structure that stores essential information about files. Each file has a unique inode number, and the inode numbers themselves are stored in an inode table. This inode table is created at the time when the file system is formatted.
On Linux systems, many things are represented as files: regular files, directories, soft links, unix sockets, named pipes, and block files (the stuff you see in /dev). Regular files have “content”, “filename” and “metadata”. Therefore the metadata includes neither the content, nor the filename itself!
Filenames would be found in directories, which are just special files that map inode numbers to string filenames. This mapping between inode to filename is a hard link (more on this later). A file must have at least one hard link to be considered accessible.
Note that the stat command can be confusing, because it does give you the filename, even though it is not part of the metadata:
user@linuxpc:~$ stat file.txt
File: file.txt
Size: 2 Blocks: 8 IO Block: 4096 regular file
Device: 812h/2066d Inode: 4615310 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/user) Gid: ( 1000/user)
Access: 2026-01-24 23:03:25.141789957 +0100
Modify: 2026-01-24 23:03:27.589770105 +0100
Change: 2026-01-24 23:03:35.761703841 +0100
Birth: 2026-01-24 23:03:25.141789957 +0100
Reaching for this metadata via some scripting language like PHP will remove the extra info that gnu stat adds:
user@linuxpc:~$ php -a
Interactive shell
php > $metadata=stat("file.txt");
php > var_dump($metadata);
array(26) {
[0]=>
int(2066)
[1]=>
int(4615310)
[2]=>
int(33204)
[3]=>
int(1)
[4]=>
int(1000)
[5]=>
int(1000)
[6]=>
int(0)
[7]=>
int(2)
[8]=>
int(1769292205)
[9]=>
int(1769292207)
[10]=>
int(1769292215)
[11]=>
int(4096)
[12]=>
int(8)
["dev"]=>
int(2066)
["ino"]=>
int(4615310)
["mode"]=>
int(33204)
["nlink"]=>
int(1)
["uid"]=>
int(1000)
["gid"]=>
int(1000)
["rdev"]=>
int(0)
["size"]=>
int(2)
["atime"]=>
int(1769292205)
["mtime"]=>
int(1769292207)
["ctime"]=>
int(1769292215)
["blksize"]=>
int(4096)
["blocks"]=>
int(8)
}
Besides not seeing the filename anymore, notice how the user id is no longer resolved to the username either. The key takeaway here is that metadata contains inode number, device number, file type, permissions, links, size, and that the combination of device number and inode number uniquely identifies a file on the system, while filenames and corresponding inode numbers are stored in directories. Directories themselves also have inode numbers.
Some of the data like mtime/Modify and ctime/Change can be confusing, since the two words feel similar, but the key difference is that:
- -> Modify tells us when the file opened and written to
- -> Change is only applicable when inode data is changed, such as a permission change
However, it can happen that a change in mtime/Modify might also trigger a change of ctime/Change because it causes the metadata itself to change.
It is possible for a system to run out of inode numbers, and if that happens, no new files can be created, even if there is otherwise free disk space to do so. This is pretty rare, but it can happen if some process creates a huge number of files. The command is df -i:
user@linuxpc:~$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
tmpfs 8220141 1167 8218974 1% /run
/dev/sdb2 14712832 1861870 12850962 13% /
tmpfs 8220141 398 8219743 1% /dev/shm
tmpfs 8220141 5 8220136 1% /run/lock
/dev/sdb1 0 0 0 - /boot/efi
/dev/sda1 61054976 804442 60250534 2% /datadisk2
/dev/sdc1 15269888 94115 15175773 1% /datadisk
tmpfs 1644028 158 1643870 1% /run/user/1000
Dealing with not having free inode numbers is usually not something you can configure your way out of, as there’s no way to increase available inode counts without formatting the storage device and reserving more space for a larger inode table. This is obviously a very difficult way to fix the issue in most cases, and probably a last resort solution.
For ext3/4 filesystems, we get one inode number per 16KiB of disk space, so in order to run out of inodes, something has to spawn really small files, as otherwise average file sizes tend to exceed this. So if this problem happens, the best thing to do is to track down where the inodes are being spent, and what’s causing the small files to spawn.
du --inodes --max-depth=[keep this number reasonably small]
If the application legitimately needs to spawn a lot of files, it may be worth to (re)format the storage device with a custom bytes to inode ratio.
Hard Links
As mentioned earlier, a file will have a hard link to the directory it is in, but we can also create hard links manually:
ln <file> <output> to create the hardlink. <output> will have the same inode number as <file>, so both of these files will link to the same content, (you should see links :2 when checking with stat).
Deleting one of these files will have no effect on the other, since the file created via the hard link is essentially a different filename for the same content, and the number of links to that content would simply drop from 2 to 1.
Hardlinks are restricted to within a storage device, and hardlinks cannot be created for directories.
A final noteworthy mention here is the relation to cp. The difference vs hardlinks and cp is that hardlinks do not create a new file, and the output generated by the hard link does not consume extra disk space, unlike cp which creates a new file with its own inode and contents. With a hardlink, if either file is updated, it will be reflected when opening the file created by the hardlink, since they have the same inode number. In an oversimplified way, a hard link is essentially just giving a second name to a file.
Symbolic links
ln -s <file> <output> to create a symbolic link (symlink). <output> will have its own inode number that is different from the inode number of <file>, and the filetype of <output> will be symbolic link.
The two files are different files, symlinks store the path of the file they were based off of and the OS knows how to look up the source file. If the source file is deleted, any usage of <output> like “grep” will return an error: (broken symlink), if <file> is deleted, and then replaced by some other file, the symlink will continue to resolve to it. Unlike hard links, symlinks can point to other storage devices.