learn/CompTIA Linux+

Linux fundamentals and the FHS

Where everything lives, and why it lives there

Lesson 4 of 76·Intro·23 min read·updated ·linuxlinux-plusfhsdistributions

On the examCompTIA Linux+ XK0-006 (V8)

  • 1.1Explain basic Linux concepts.Domain 1.0 System Management·23% of the exam

All 29 objectives, and which are covered

Before this

What you will be able to do

  • Name the purpose of each top-level directory and predict where a given file belongs
  • Explain what the usr-merge changed and which tools still behave as if it did not happen
  • Identify a distribution and its family from the system itself rather than from memory
  • Choose the right package query command for the family you are actually on

Before you read. A Linux machine has run out of disk space. You have not installed anything new and you have not saved any files. Something has been quietly filling the disk on its own for months. Which one directory would you look in first, and what kind of thing would you expect to find there?

You are not expected to know the answer yet. Have a guess anyway.

On Windows your programs live in C:\Program Files and your documents live in C:\Users\you\Documents. Somebody decided that once, and now everybody follows it. Linux has the same kind of agreement, written down as a standard, and it is what this topic is about.

Here is why it is worth an hour rather than a glance. On Windows the folder names are mostly labels. In Linux they encode decisions: what is safe to share between machines, what can be locked read-only, and what is deliberately thrown away every time the machine restarts. Once you can see the decisions, you can work out where a file belongs rather than memorising twenty folder names.

Some words you will need

directory
A folder. Linux documentation says directory, so this track does too.
path
The address of a file, written with forward slashes: /etc/hosts. Linux uses / where Windows uses \.
root directory
The very top, written as a single /. Everything on the system hangs underneath it. There are no drive letters; a second disk appears as a directory somewhere under / rather than as D:.
distribution
A packaged version of Linux, such as Ubuntu, Debian, or Red Hat Enterprise Linux. Often shortened to "distro". They share almost everything and differ in specific, testable ways.
package
Installable software, roughly the equivalent of an installer on Windows. The program that installs them is a package manager.

What breaks without this

Three failures, all cheap to avoid and expensive to debug.

You put a file somewhere that gets wiped. You need to save something, you find a directory you are allowed to write to, and you use it. If that directory was /run or /tmp, the system deletes the contents on reboot. On purpose. Your file works fine for a week, someone restarts the machine, and it is gone with no error and nothing in a log.

You write instructions that only work on half of Linux. The command to install software on Ubuntu is not the command to install software on Red Hat. Neither are some of the paths around it. A script you tested on one machine fails on another, and the error message names a missing program rather than the assumption you made.

You trust a command that quietly gives the wrong answer. There is one further down this page that does exactly that, and it catches people with years of experience, because the command is correct and the answer is still wrong.

The mental model

Start with one directory rather than the whole tree.

Say you install a web server. Its program file has to go somewhere, its settings file has to go somewhere, and the log it writes has to go somewhere. Three files, three different kinds of thing:

  • The program is identical on every machine that installed the same version. Nobody edits it. It gets replaced when you upgrade.
  • The settings are specific to this machine, and you do edit them. Change the settings and you change what this server does.
  • The log grows on its own, forever, without anybody touching it.

Linux puts those in three different places for that reason: the program in /usr, the settings in /etc, the log in /var. Not by tradition. Because they behave differently.

Generalise that and you have the whole standard. Every directory sits somewhere on two questions: does the content change by itself while the machine runs, and is it the same on every machine or specific to this one?

The two axes the filesystem hierarchy sorts directories along A two by two grid. Static and shareable holds /usr. Static and local holds /etc and /boot. Variable and shareable holds /var/mail and /home. Variable and local holds /var/log, /var/run and /tmp. shareable local static variable /usr /etc /boot /home /var/log /run /tmp mountable read-only this machine's identity exportable over the network /run is cleared every boot
The FHS is not an arbitrary list. It sorts by what can be shared and what changes.

Read each corner as an answer to those two questions:

  • /usr does not change by itself, and is the same everywhere. This is where installed programs live. Because nothing on the running machine writes to it, it can be made read-only, or stored once and shared with many machines.
  • /etc does not change by itself, but is specific to this machine. The settings. This is the directory that makes this server this server, which is why it is the one to back up.
  • /var changes constantly. Logs, mail queues, caches, databases. It grows whether you touch it or not, which is why it is the usual reason a machine runs out of disk.
  • /run changes constantly and is thrown away on every restart. It holds notes the running system keeps for itself, such as which processes are alive. Nothing durable should ever be put here.
If you already administer Linux: the standard's own vocabulary

The two axes are what the Filesystem Hierarchy Standard calls static versus variable and shareable versus unshareable. The practical consequences are the ones worth carrying: /usr being static and shareable is what makes a read-only /usr and network-mounted /usr possible, and it is the assumption behind image-based systems and containers, where the OS layer is immutable and everything writable is a mount.

/run is a tmpfs, a filesystem that exists only in memory, which is why it is empty after boot without anything having to delete it. Before it existed this state lived in /var/run, which on modern systems is a symlink to /run for compatibility.

The directories worth committing to memory are the ones where getting it wrong costs you something:

Directory Holds The reason it matters
/etc System-wide configuration, no binaries The machine’s identity. Back this up.
/usr Installed software: /usr/bin, /usr/lib, /usr/share Owned by the package manager. Do not hand-edit.
/var Logs, spools, caches, service data Fills up. Give it its own filesystem on a server.
/opt Self-contained third-party software Vendor drops that do not follow the FHS live here.
/srv Data served by this system Web roots and exports, by convention rather than enforcement.
/boot Kernel, initramfs, bootloader config Often a small separate partition, which is why it fills.
/proc Kernel and process interface Not on disk. Files here are generated on read.
/sys Device and driver interface Also not on disk.
/run Runtime state, PID files, sockets Cleared every boot. Never put anything durable here.
/tmp Scratch space May be cleared on boot or on a timer. Assume it will be.

/proc and /sys catch people out because they look like directories and are not. Nothing there is stored anywhere; reading a file runs kernel code that produces the answer. That is why cat /proc/cpuinfo is instant and why you cannot back /proc up.

The usr-merge, and what it broke

For most of Linux’s history /bin and /usr/bin were genuinely different directories, split because early systems had a small root disk and mounted /usr separately. That distinction stopped being useful once initramfs existed, and every mainstream distribution has since collapsed it: /bin, /sbin, /lib, and /lib64 are now symlinks into /usr.

You can see it directly.

Here is the root of a current Debian system. Read the first character of each line: d for a directory, l for a symbolic link.

/bin, /lib, and /sbin have been separate directories since the 1970s. On a system from the last few years, are they still directories?
# Debian 13 (trixie), x86_64
$ ls -l / | head -25
total 0
lrwxrwxrwx.   1 root   root      7 Jul  4 09:05 bin -> usr/bin
drwxr-xr-x.   2 root   root      6 Jul  4 09:05 boot
drwxr-xr-x.   5 root   root    340 Aug  7 19:25 dev
drwxr-xr-x.   1 root   root     31 Aug  7 19:25 etc
drwxr-xr-x.   2 root   root      6 Jul  4 09:05 home
lrwxrwxrwx.   1 root   root      7 Jul  4 09:05 lib -> usr/lib
lrwxrwxrwx.   1 root   root      9 Jul  4 09:05 lib64 -> usr/lib64
drwxr-xr-x.   2 root   root      6 Aug  3 00:00 media
drwxr-xr-x.   2 root   root      6 Aug  3 00:00 mnt
drwxr-xr-x.   2 root   root      6 Aug  3 00:00 opt
dr-xr-xr-x. 216 nobody nogroup   0 Aug  7 19:25 proc
drwx------.   2 root   root     37 Aug  3 00:00 root
drwxr-xr-x.   1 root   root     42 Aug  7 19:25 run
lrwxrwxrwx.   1 root   root      8 Jul  4 09:05 sbin -> usr/sbin
drwxr-xr-x.   2 root   root      6 Aug  3 00:00 srv
dr-xr-xr-x.  12 nobody nogroup   0 Aug  7 19:25 sys
drwxrwxrwt.   2 root   root      6 Aug  3 00:00 tmp
drwxr-xr-x.  12 root   root    133 Aug  3 00:00 usr
drwxr-xr-x.  11 root   root    139 Aug  3 00:00 var

Three of them are symlinks now. bin, lib, lib64, and sbin all begin with l and point into /usr. Fifty years of documentation, scripts, and exam material describe them as directories, and on any current system they are not, which is the whole of the next few paragraphs.

The RHEL family did the same thing, earlier. AlmaLinux 10 looks almost identical, with the addition of /afs and without a /boot entry inside a container image:

# AlmaLinux 10.2, x86_64
$ uname -m; ls -l / | head -6
x86_64
total 8
dr-xr-xr-x.   2 root   root      6 Apr  2  2025 afs
lrwxrwxrwx.   1 root   root      7 Apr  2  2025 bin -> usr/bin
drwxr-xr-x.   5 root   root    340 Aug  7 19:27 dev
drwxr-xr-x.  43 root   root   4096 Jun  2 11:08 etc
drwxr-xr-x.   2 root   root      6 Apr  2  2025 home

The practical consequence is that /bin/ls and /usr/bin/ls are the same file. The consequence people get caught by is what happens when you ask a package manager about it.

Telling the families apart from the system itself

The split that matters most on this exam is RPM-based against dpkg-based, because it decides which package manager, which config paths, and often which service names you get. Do not infer it from the hostname. Read it.

/etc/os-release is the answer, and it is the one file that is present and consistent everywhere, because systemd standardised it.

# AlmaLinux 10.2, x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
almalinux | 10.2 | AlmaLinux 10.2 (Lavender Lion)
# Debian 13 (trixie), x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
debian | 13 | Debian GNU/Linux 13 (trixie)
# Ubuntu 24.04 LTS, x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
ubuntu | 24.04 | Ubuntu 24.04.4 LTS
# openSUSE Leap 16.0, x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
opensuse-leap | 16.0 | openSUSE Leap 16.0

The file is shell syntax on purpose, so sourcing it is the intended way to read it rather than a trick. For derivative distributions there is also ID_LIKE, which is how you detect a family rather than a specific product: Ubuntu reports ID_LIKE=debian, AlmaLinux reports ID_LIKE="rhel centos fedora".

If you already administer Linux: /opt, /usr/local, and /srv, and who is allowed to write where

Three directories that all mean “not from the distribution”, with a real division of labour the standard is specific about.

/usr/local is for software you built or installed yourself, and it mirrors /usr underneath: /usr/local/bin, /usr/local/lib, /usr/local/etc. Nothing from a package manager writes here, which is exactly the point: it is the one place you own on a machine whose /usr belongs to the distribution. make install defaults to it for that reason.

/opt is for self-contained third-party packages, each in its own directory named for the vendor or product: /opt/vendorname/. Commercial software uses it because it wants one tree it can install and remove atomically rather than files scattered through the hierarchy.

/srv is for data this machine serves. Web roots, FTP trees, exported shares. It is the one that gets ignored, with web content ending up in /var/www, which is a Debian convention rather than the standard’s answer. Either works; consistency across a fleet is what matters.

The operational value of the split: a file under /usr that no package owns is an anomaly worth investigating; the same file under /usr/local or /opt is somebody doing the right thing. That distinction is what makes rpm -qf and dpkg -S returning nothing meaningful rather than merely true, and it is the lesson 08 point arriving from a different direction.

Across distributions

RPM family dpkg family
Examples on this exam RHEL, AlmaLinux, Rocky, Fedora, openSUSE, SLES Debian, Ubuntu
Low-level tool rpm dpkg
Resolver dnf (zypper on SUSE) apt
Which package owns a path rpm -qf PATH dpkg -S PATH
Package file extension .rpm .deb
Web server package httpd apache2

That last row is the shape of a whole class of differences: the same software, a different package name, and therefore a different service name and a different config path. It is not a detail you can reason your way to, which is why the exam asks about it.

Two ways of splitting the same distributions, and the track uses both. The table above splits on package format, which is why it says RPM family and dpkg family. Every later topic says RHEL family and Debian family instead, because most of what differs between machines is a decision the distribution made rather than a consequence of rpm or dpkg: which group grants sudo, where the auth log lives, what the default root filesystem is.

The two groupings are nearly the same and not quite. openSUSE and SLES use rpm, so they sit in the RPM family, and they are not RHEL derivatives: they resolve packages with zypper rather than dnf, and they default to Btrfs on root where RHEL uses XFS. When this track says “RHEL family” it means RHEL, AlmaLinux, Rocky, and Fedora. When a difference is genuinely about the package format, it says RPM family and includes SUSE.

One exception inside that family, since this track keeps saying the RHEL side uses XFS. Fedora’s desktop variants have installed onto Btrfs since Fedora 33. Fedora Server, Cloud, IoT, and CoreOS were excluded and still use XFS, so the claim holds for the server installs this exam is about and not for a Fedora Workstation laptop.

If you already administer Linux: what the usr-merge actually bought, and the three places it still bites

Collapsing /bin into /usr/bin looks like tidying. The motivation was operational, and knowing it explains why every distribution did it within a few years of each other.

The original split was a 1970s disk-space accident. Ken Thompson and Dennis Ritchie filled the RP03 holding / and moved the overflow to the second disk, mounted at /usr. The rule that followed, “/bin holds what you need before /usr is mounted”, was rationalisation after the fact, and it stopped being true once initramfs took over early boot in the 2000s.

What the merge bought:

  • /usr can be a single read-only, verifiable, shareable image. Everything the distribution ships is in one subtree, so it can be mounted read-only, checksummed, atomically swapped, or shared between containers. That is the foundation of image-based systems like Fedora CoreOS and of ostree generally.
  • A clean split between vendor data and machine state. /usr is the vendor’s; /etc and /var are the machine’s. Factory reset becomes “discard /etc and /var”, which is genuinely useful.
  • The end of “which copy of this binary is real”. Two bin directories on $PATH was a real source of confusion.

Three places it still bites:

dpkg -S /bin/ls fails on Debian while dpkg -S /usr/bin/ls works. The package database records the canonical path, and dpkg does not resolve the symlink for you. rpm -qf does resolve it, so the same query behaves differently by family, worth knowing before concluding a file is unowned.

Shebangs written #!/bin/bash still work, because the symlink resolves, but a script hardcoding /bin in a comparison against $PATH entries will not match. Anything doing string equality on paths is suspect.

find / -name ls reports it twice unless you use -xdev or account for the symlink, because the tree really is reachable by two routes. That inflates any audit script that counts files, including some setuid inventories.

The one exception nobody merged is /sbin. It was merged into /usr/sbin, but Debian went further in trixie and made /usr/sbin a symlink to /usr/bin too, on the grounds that the split between “commands for administrators” and “commands for everyone” was never enforced by anything. So on a current Debian all four paths reach one directory.

Server architectures, and the two names for each one

Four architectures are worth knowing by name: x86 (32-bit), x86_64 (also written AMD64, the 64-bit extension and the default for servers), AArch64 (64-bit ARM, now common in cloud instances and in every Apple Silicon Mac), and RISC-V, an open instruction set architecture that is still mostly outside production but is named because it is arriving.

The trap is that the same machine reports two different architecture strings depending on who you ask.

# Debian 13 (trixie), x86_64
$ uname -m; dpkg --print-architecture
x86_64
amd64
# Debian 13 (trixie), aarch64
$ uname -m; dpkg --print-architecture
aarch64
arm64
# AlmaLinux 10.2, x86_64
$ uname -m; rpm -E "%{_arch}"
x86_64
x86_64

uname -m reports the kernel’s name for the machine. dpkg uses Debian’s own architecture names, which are different words for the same thing: amd64 for x86_64, arm64 for aarch64. rpm uses the kernel’s name unchanged.

So on a Debian-family system, a script that compares uname -m against a package architecture will never match, and the mismatch is not an error - it is two naming schemes for one machine. When you need the packaging name, ask the packaging tool.

The graphical stack, briefly

Servers usually have none of this, but the exam names the pieces and they stack in a specific order:

Piece Job Examples
Display server Talks to input devices and the screen; draws nothing itself X.Org (X11), Wayland compositor
Display manager The graphical login prompt, starts the session GDM, SDDM, LightDM
Window manager Decorates, moves, and stacks windows Mutter, KWin, i3
Desktop environment The window manager plus a full set of applications GNOME, KDE Plasma, Xfce

The distinction that gets tested is X versus Wayland. X.Org is a display server that clients connect to over a socket, with the window manager a separate client telling X where to put things. Wayland collapses that: the compositor is the display server and the window manager at once, and clients draw their own windows. That is why “X forwarding over SSH” has no direct Wayland equivalent, and why disabling X forwarding shows up as a hardening step later in this track.

Software licensing

Four terms, and the difference between two of them is the one people get wrong.

  • Free software is about the recipient’s rights: to run, study, modify, and redistribute. “Free” as in freedom, not price.
  • Open source describes largely the same set of licenses with the emphasis on the development model rather than the ethics.
  • Copyleft is a specific mechanism, not a synonym for either. A copyleft license requires that derivative works carry the same license, so the freedoms cannot be stripped downstream. The GPL is copyleft; MIT and BSD are open source and permissive, which means a derivative may be closed.
  • Proprietary software withholds some of those rights, typically the source itself.

The practical consequence in a work setting: permissive and copyleft licenses have genuinely different obligations when you ship a product built on them, which is why the distinction is on a sysadmin exam at all.

If you already administer Linux: which directories are memory, and which are not really files

df -h on a modern machine lists a dozen filesystems that are not on any disk, and knowing which is which prevents a category of confusion.

tmpfs lives in RAM and is gone at reboot. /run is always one, /dev/shm is always one, and /tmp is one on most current distributions. Two consequences: writing a large file to /tmp consumes memory, not disk, and a process that fills /dev/shm can starve the machine while df on the root filesystem looks healthy. findmnt -t tmpfs lists them.

/proc is not a filesystem at all in any meaningful sense. Every file in it is generated by the kernel at the moment you read it, which is why /proc/meminfo has a size of zero and content that changes every time. /proc/<pid>/ is the per-process view (cmdline, environ, cwd, fd/) and ls -l /proc/1234/fd listing open file descriptors is how you find what is holding a deleted file open when df and du disagree.

/sys is the device model, one file per attribute, and it is writable in places. Tuning knobs live under /sys/class/ and /sys/block/, which is how you change a disk’s I/O scheduler or a network card’s queue length without a reboot, and lose it at the next one unless it is written into udev rules or sysfs.conf.

/boot may be a separate real filesystem with very little room, and a full /boot is what stops a kernel update completing. It is worth being on the list of things to check before a patch window, alongside the initramfs question from lesson 09.

Prove it

Three checks that tell you what you are on and confirm the layout is what you think it is.

# Which distribution and family
. /etc/os-release && echo "$ID (${ID_LIKE:-none}) $VERSION_ID"

# Confirm the usr-merge is in effect: these should be symlinks, not directories
ls -ld /bin /sbin /lib

# Confirm /bin/ls and /usr/bin/ls are one file, not two copies
stat -c '%i %n' /bin/ls /usr/bin/ls

The third one is the check worth internalising. stat -c '%i' prints the inode number. If the two paths report the same inode, they are the same file on the same filesystem, and any difference in how a tool answers questions about them is coming from the tool, not from the disk.

What trips people up

1. dpkg -S and rpm -qf disagree about /bin/ls

Both systems have /bin symlinked to usr/bin. Both have exactly one ls. Ask each package manager who owns /bin/ls and you get different answers.

One of these two commands fails. Which, and what does that tell you about where each tool looks?
# AlmaLinux 10.2, x86_64
$ rpm -qf /bin/ls; rpm -qf /usr/bin/ls
coreutils-single-9.5-7.el10.x86_64
coreutils-single-9.5-7.el10.x86_64
# Debian 13 (trixie), x86_64
$ dpkg -S /bin/ls; echo "exit=$?"; dpkg -S /usr/bin/ls
dpkg-query: no path found matching pattern /bin/ls
exit=1
coreutils: /usr/bin/ls

rpm -qf resolves the path on the live filesystem before looking it up, so the symlink is followed and both spellings land on the same entry. dpkg -S does a string match against the paths recorded in the package database, and Debian’s database records /usr/bin/ls. /bin/ls is not in the database because no package ever claimed that spelling.

Neither tool is broken. They are answering different questions: rpm asks the filesystem, dpkg asks its own records.

What this means in practice: dpkg -S wants the canonical path. When a lookup comes back empty on a Debian-family system, resolve the path first rather than concluding the file is unpackaged.

dpkg -S "$(realpath /bin/ls)"

2. /tmp and /run are not places to keep things

/run is a tmpfs and is empty after every boot. That is its purpose: it holds runtime state such as PID files and sockets that should not survive a restart. /tmp may also be cleared, either at boot or on a timer, and which of those happens depends on the distribution.

The failure is slow. A service writes state to one of them, works for as long as the machine stays up, and loses everything at the next reboot. Durable service data belongs in /var/lib/<service>, and that is what packages use.

3. /proc and /sys are not files, so normal tools mislead you

ls -l /proc/cpuinfo reports a size of zero, and every file under /proc and /sys reports zero. The content does not exist until you read it, at which point the kernel generates it. So du reports nothing, backup tools either skip these trees or hang on them, and a file size tells you nothing about whether there is content.

Read them with cat, and exclude them from anything that walks the filesystem.

4. Confusing /usr/local with /usr

/usr belongs to the package manager. /usr/local belongs to you, and package managers do not write there. Software you compile from source installs to /usr/local by default for exactly this reason: it keeps hand-built binaries out of the tree that a package upgrade will overwrite.

If you put something in /usr/bin by hand, the next package upgrade that ships that path will overwrite it without asking. That is not a bug; you wrote into someone else’s directory.

Work it through

You inherit a Linux server with no documentation. A monitoring check is failing with a message about a missing binary at /usr/bin/healthcheck. You need to know whether that file was installed by a package, dropped there by hand, or never existed.

Reason it out before reading on.

Establish which family you are on, because it decides which query you can run:

. /etc/os-release && echo "$ID ${ID_LIKE:-}"

Say it reports debian. That means dpkg, not rpm.

Ask whether the path exists at all, and if it does, resolve it:

ls -l /usr/bin/healthcheck

Ask the package database who owns it. On the dpkg family, give it the canonical path, because of trip-up one:

dpkg -S "$(realpath /usr/bin/healthcheck 2>/dev/null || echo /usr/bin/healthcheck)"

Three outcomes and what each one tells you:

  • A package name comes back. The file belongs to a package and something removed or replaced it. Reinstalling that package restores it.
  • no path found matching pattern, and the file exists. Somebody put it there by hand. It is not managed, it will not be upgraded, and it will not be reinstalled by anything. It also belongs in /usr/local/bin, not /usr/bin.
  • The file does not exist and no package claims it. The check is pointing at something that was never installed on this machine. The bug is in the monitoring configuration, not on the server.

The reasoning that matters: you did not guess, and you did not run a command that only works on the other family. You read the system, chose the query that matches it, and let the outcome narrow the possibilities.

Try it

Optional, and only worth doing if you have a VM or a container to hand.

Start from a Debian-family system and reproduce the usr-merge disagreement deliberately.

  1. Confirm /bin is a symlink and that /bin/ls and /usr/bin/ls share an inode.
  2. Run dpkg -S /bin/ls and confirm it fails.
  3. Make it succeed without changing anything on disk.

For step 3 you need the same idea as trip-up one: the database stores canonical paths, so the query has to supply one.

Verification step. You have it right when the same file produces a package name through one spelling and an error through the other, and you can say in one sentence why, without using the word “bug”.

Check yourself

A file must survive a reboot and is service state rather than configuration. Which directory does it belong in, and which two are the tempting wrong answers?

/var/lib/<service>. That is where packages put exactly this kind of thing: data a service owns and needs to still be there tomorrow.

The tempting wrong answers are /tmp and /run, because both are writable and convenient and neither complains. /run is emptied on every boot by design, and /tmp may be cleared on boot or on a timer depending on the distribution.

/etc is the third near-miss. It survives reboots perfectly well, but it is for configuration you edit, not for state a service writes, and mixing the two makes backups and version control unpleasant.

ls -l /proc/meminfo reports a size of 0 bytes. Is the file empty?

No. It is not a file in the usual sense at all.

Everything under /proc is generated by the kernel at the moment you read it, so there is nothing sitting on a disk to have a size. The kernel reports zero because reporting anything else would be a guess.

Read it with cat and you get plenty of content. The practical consequences: du sees nothing there, and backup tools should skip /proc and /sys entirely, because walking them is at best pointless and at worst a hang.

rpm is not installed and dpkg -S returns nothing for a path you can see with ls. Name two explanations and the command that tells them apart.

Either the file genuinely is not owned by any package, meaning somebody put it there by hand, or you gave dpkg a path it does not have on file, because the database records the canonical /usr/... spelling and you handed it a usr-merge symlink.

dpkg -S "$(realpath /path/to/file)" distinguishes them. Resolve the path first; if it now returns a package, you had spelling, not an unmanaged file. If it still returns nothing, the file is genuinely unmanaged, which also means nothing will ever upgrade or reinstall it.

What does ID_LIKE in /etc/os-release give you that ID does not?

ID tells you the specific product: ubuntu, almalinux, opensuse-leap. ID_LIKE tells you the family it descends from, so Ubuntu reports ID_LIKE=debian and AlmaLinux reports ID_LIKE="rhel centos fedora".

That is the difference between a script that works on Ubuntu and a script that works on anything Debian-shaped. Match on ID_LIKE when you care about which package manager exists, and on ID only when you genuinely need one specific distribution.

Worth knowing the edge: Debian itself has no ID_LIKE, because it is not like anything else, it is the thing others are like. A check that only reads ID_LIKE will miss Debian entirely.

References

Every block above with a distribution and architecture header was captured by running the command on a Debian 13 (trixie) container, an AlmaLinux 10.2 container, an Ubuntu 24.04 LTS container and an openSUSE Leap 16.0 container. Blocks without one are illustrative.