chattr and lsattr: The Filesystem Flags Even root Has to Think Twice About

2026-07-08

Every Linux filesystem hides a second layer of metadata beyond the familiar rwx bits: inode flags. They're set with chattr, inspected with lsattr, and they've shipped with ext2/3/4, XFS, Btrfs, and f2fs for decades. Most developers have never touched them, which is a shame, because they solve problems that chmod and sudo genuinely cannot.

Start with a look at what you already have:

$ lsattr /etc/resolv.conf
--------------e------- /etc/resolv.conf

That e is the "extents" flag ext4 sets automatically. Interesting, but the real fun is the flags you set yourself.

+i (immutable). The file cannot be modified, renamed, deleted, hard-linked to, or have its metadata touched. Not by you, not by scripts, not by the root user — not until someone runs chattr -i first:

# The classic: stop NetworkManager from clobbering DNS
$ sudo chattr +i /etc/resolv.conf
$ sudo rm /etc/resolv.conf
rm: cannot remove '/etc/resolv.conf': Operation not permitted
$ sudo bash -c 'echo hi > /etc/resolv.conf'
bash: /etc/resolv.conf: Operation not permitted

Yes, root can undo it — but only after explicitly flipping the flag. It's a speed bump against sloppy scripts, config-management tools that "know better," and your own 2 a.m. fingers. That's the whole point.

+a (append-only). Writes at the end only. Perfect for audit logs, chain-of-custody files, or anywhere you want to guarantee history isn't rewritten:

$ sudo chattr +a /var/log/audit/audit.log
$ echo 'tampering' >  /var/log/audit/audit.log   # fails
$ echo 'tampering' >> /var/log/audit/audit.log   # works

+C (no copy-on-write, Btrfs). The single most important flag on Btrfs. Set it on a directory before creating VM images, database files, or anything with random in-place writes, and you'll dodge the fragmentation catastrophe that CoW inflicts on those workloads:

$ mkdir /var/lib/libvirt/images
$ sudo chattr +C /var/lib/libvirt/images   # inherited by new files
$ lsattr -d /var/lib/libvirt/images
---------------C------ /var/lib/libvirt/images

+A (no atime updates). A cheap-and-cheerful perf trick for hot files when you haven't gotten around to mounting with noatime.

+d (no dump). Tells dump(8) — and, more usefully today, backup tools that honor it — to skip this file. Handy for scratch caches inside otherwise-backed-up trees.

Recursive setup is straightforward:

$ sudo chattr -R +i /etc/nginx/       # freeze the whole config tree
$ sudo lsattr -R /etc/nginx/ | grep -- '-i-'

Why is this better than chmod 000 or moving the file to root's home? Because chmod is a permissions statement — anyone with the right UID or CAP_DAC_OVERRIDE walks right through it. Immutable is a capability check: the kernel refuses the write regardless of DAC, until CAP_LINUX_IMMUTABLE is used to clear the flag. In hardened containers, unprivileged namespaces, and rootless setups, that flag simply cannot be flipped from inside.

Two gotchas to keep in your back pocket. First: tar, rsync, and friends restore file contents but not inode flags — you need --xattrs-aware tools or a post-restore chattr pass. Second: an immutable file will happily break an apt or dnf upgrade that expects to overwrite it. Set an alert, or use +a where you can get away with it.

Key Takeaway: chattr +i and +a give you a kernel-enforced "do not touch" that survives root, sudo, and sloppy scripts — a layer of protection chmod was never designed to provide.

All newsletters