Skip to main content

Sysadmin basics: Create hard links in Linux

A hard link looks like a new file but points back to the data in the original file.

Image by Kerstin Riemer from Pixabay

In previous articles, I discussed how to create and delete files and directories and copy, move, and rename them. These are daily sysadmin tasks, and knowing how to do them brings you one step closer to becoming an enterprise Linux professional who understands the concepts behind every system operation. This gives you an advantage when developing efficient operational tasks. Continuing with that idea, this article discusses hard links in Linux with examples. My next article covers how to create symbolic (soft) links.

[ Keep your most commonly used commands handy with the Linux commands cheat sheet. ]

What are hard links, and how do sysadmins use them? Before covering links, I'll provide some background concepts about using them.

Understand index nodes

My buddy Tyler Carrigan did a great job writing about index notes (inodes), and that article will expand your knowledge on something I touch on here. Understanding inodes is important because of the intrinsic nature of file and directory links in the Linux filesystem.

Inodes are data structures that describe a filesystem object, such as a file or directory, and store all the metadata pertaining to the file or directory (such as time stamps, block maps, or extended attributes). You can read more about this in the kernel documentation. Each inode has its own address in the system. When you manipulate hard and soft links, you also deal with the inodes of the original files and directories.

Consider the following information from my virtual machine's (VM's) single disk:

$ sudo fdisk -l | head -10
Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 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
Disklabel type: dos
Disk identifier: 0x7a1c2d8b

Device     Boot   Start      End  Sectors Size Id Type
/dev/vda1  *       2048  2099199  2097152   1G 83 Linux
/dev/vda2       2099200 41943039 39843840  19G 8e Linux LVM

My VM's disk has a size of 20GiB. This available space is divided into 41,943,040 sectors of 512B each, with a disk block size of 4KiB. This means that every single file I create, whether smaller or larger than 4KiB, will consume at least eight sectors for each corresponding 4KiB chunk of disk block size consumed by my file. The sector addresses consumed by the file will be indexed and referenced by the corresponding inode address that the file receives from the filesystem. This makes it easy to find the file and its metadata on my system.

[ Learn how to manage your Linux environment for success. ]

I'll demonstrate by creating a simple directory and displaying its inode and size information:

$ mkdir dir

$ ls -li
total 0
25606589 drwxrwxr-x. 2 localuser localuser 6 set 19 15:21 dir

$ stat dir

  File: dir
  Size: 6         	Blocks: 0          IO Block: 4096   directory
Device: fd00h/64768d	Inode: 25606589    Links: 2
Access: (0775/drwxrwxr-x)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:21:27.616000000 -0300
Modify: 2022-09-19 15:21:27.616000000 -0300
Change: 2022-09-19 15:21:27.616000000 -0300
 Birth: 2022-09-19 15:21:27.616000000 -0300

Use the ls command with the -i option to display inode information. Above, you can see that the inode address of the given dir directory is 25606589. I can confirm that and check other metadata information with the stat command. I'll create a file inside this directory and check the information:

$ touch dir/file

$ stat dir

  File: dir
  Size: 18        	Blocks: 0          IO Block: 4096   directory
Device: fd00h/64768d	Inode: 25606589    Links: 2
Access: (0775/drwxrwxr-x)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:29:37.495000000 -0300
Modify: 2022-09-19 15:29:40.276000000 -0300
Change: 2022-09-19 15:29:40.276000000 -0300
 Birth: 2022-09-19 15:21:27.616000000 -0300

$ ls -li dir/file

25606591 -rw-rw-r--. 1 localuser localuser 0 set 19 15:29 dir/file

$ stat dir/file

  File: dir/file
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: fd00h/64768d	Inode: 25606591    Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:29:40.276000000 -0300
Modify: 2022-09-19 15:29:40.276000000 -0300
Change: 2022-09-19 15:29:40.276000000 -0300
 Birth: 2022-09-19 15:29:40.276000000 -0300

The file has its own inode address of 25606591. Its size is 0 because it has no contents yet. I'll add a single byte of data and see what happens:

$ echo 1 > dir/file

$ stat dir/file

  File: dir/file
  Size: 2         	Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 25606591    Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:29:40.276000000 -0300
Modify: 2022-09-19 15:36:18.812000000 -0300
Change: 2022-09-19 15:36:18.812000000 -0300
 Birth: 2022-09-19 15:29:40.276000000 -0300

Now the file is consuming eight sectors, even though I only added 1B of data. That's because I'm working with the default 4KiB block disk size and 512B sector size. But the inode address remains the same, and that's important because, regardless of the size of the file, it will always be referenced by its inode address, not each individual sector it consumes.

Also, if I copy of this file, the duplicate will have its own inode address and will consume eight extra sectors:

$ cp dir/file dir/copy

$ stat dir/file

  File: dir/file
  Size: 2         	Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 25606591    Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:42:17.328000000 -0300
Modify: 2022-09-19 15:36:18.812000000 -0300
Change: 2022-09-19 15:36:18.812000000 -0300
 Birth: 2022-09-19 15:29:40.276000000 -0300

$ stat dir/copy

  File: dir/copy
  Size: 2         	Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 25811328    Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:42:17.328000000 -0300
Modify: 2022-09-19 15:42:17.328000000 -0300
Change: 2022-09-19 15:42:17.328000000 -0300
 Birth: 2022-09-19 15:42:17.328000000 -0300

Now that you understand the concept, what if you just want to reference a file with a different name or in a different location without changing the original inode address or any other metadata information? Or perhaps you want to change the inode address but want access to the same information provided by the original file or directory?

That's where the link creation feature comes into play! There are two different ways to create links to files or directories in the system: hard links and symbolic links. This article covers hard links.

Create hard links

When you're creating a hard link, you're creating another file (with a different name) that points to the exact same data as the original file. That means it acts as the original file, and you cannot differentiate between the new hard link and the original name of the file.

It's basically a mirror copy of the original file. They both have the same content, permissions, and inode address. Be aware that any changes made in one file affect the other file in the same way, except for deletion, which will not impact the original data. When you delete the original file, and there's at least one hard link alive, you can still access the original data until all hard links have been deleted.

However, hard links have limitations. You cannot create hard links for directories or create a hard link in a different filesystem from the original file.

[ Get the guide to installing applications on Linux. ]

The ln command creates links. Use the -h option to see its available parameters.

To create a hard link, type ln {source} {target}, like this:

$ ls /tmp/

$ ln dir/file /tmp/hard

$ ls -l /tmp/

total 4
-rw-rw-r--. 2 localuser localuser 2 set 19 15:36 hard

To prove this hard link is a mirror of the original file, check its metadata information, content, and inode address:

$ ls -i dir/file

25606591 dir/file

$ ls -i /tmp/hard

25606591 /tmp/hard

$ stat dir/file

  File: dir/file
  Size: 2         	Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 25606591    Links: 2
Access: (0664/-rw-rw-r--)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:42:17.328000000 -0300
Modify: 2022-09-19 15:36:18.812000000 -0300
Change: 2022-09-19 16:49:20.216000000 -0300
 Birth: 2022-09-19 15:29:40.276000000 -0300

$ stat /tmp/hard

  File: /tmp/hard
  Size: 2         	Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 25606591    Links: 2
Access: (0664/-rw-rw-r--)  Uid: ( 1000/localuser)   Gid: ( 1000/localuser)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2022-09-19 15:42:17.328000000 -0300
Modify: 2022-09-19 15:36:18.812000000 -0300
Change: 2022-09-19 16:49:20.216000000 -0300
 Birth: 2022-09-19 15:29:40.276000000 -0300

Both results have the same information. Adding any data to the hard link will change the original file as well:

$ cat dir/file

1

$ cat /tmp/hard

1

$ echo 0 > /tmp/hard

$ cat /tmp/hard

0

$ cat dir/file

0

If you delete the original file, the hard link remains with the original data and you can keep using it:

$ rm dir/file

$ ls dir/

$ cat /tmp/hard

0

The same would happen if you deleted the hard link and kept the original file.

Wrap up

In certain scenarios, it is better to use hard links instead of copying the original data, which will duplicate the content in your system and consume sectors. Read my follow up article on how symbolic links work and how they compare to hard links.

[ Cheat sheet: Get a list of Linux utilities and commands for managing servers and networks. ]

Author’s photo

Alexon Oliveira

Alexon has been working as a Senior Technical Account Manager at Red Hat since 2018, working in the Customer Success organization focusing on Infrastructure and Management, Integration and Automation, Cloud Computing, and Storage Solutions. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.