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:

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.

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.

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.