Extended Attributes and Filesystem Attributes
Extended attributes (xattrs) and filesystem attribute flags add control beyond standard Unix permissions and ACLs. On an Ubuntu WordPress server, they are most useful for locking down high-value files (like wp-config.php), preventing unexpected changes during incidents, and preserving metadata across backups and deploys. This page explains the mental model and shows safe, reversible commands for lsattr/chattr, getfattr/setfattr, and getcap/setcap.
Prerequisites
- Ubuntu server access (root or
sudo). - A filesystem and mount options that support xattrs and attribute flags (ext4 does on modern Ubuntu).
- Packages:
sudo apt update
sudo apt install -y attr libcap2-bin rsync tar
- Know your WordPress document root (example:
/var/www/example.com/public_html).
Core Concepts
| Layer | What it controls | Tools |
|---|---|---|
| Permissions | Read/write/execute for owner, group, and others | chmod, chown, chgrp |
| ACLs | Per-user and per-group access rules | getfacl, setfacl |
| Filesystem attributes | Filesystem-enforced flags like immutable and append-only | lsattr, chattr |
| Extended attributes (xattrs) | Key-value metadata attached to files | getfattr, setfattr |
| Capabilities | Fine-grained privileges stored in security.capability | getcap, setcap |
Permissions decide access, ACLs refine it, filesystem attributes can block changes until the flag is removed, and xattrs/capabilities attach metadata or narrowly scoped privileges.
Filesystem Attributes (lsattr, chattr)
lsattr: list attributes
Syntax
lsattr [OPTIONS] [files...]
Common options
| Option | Meaning |
|---|---|
-R | Recurse into directories |
-a | Include hidden files |
-d | List the directory itself, not its contents |
-l | Show long attribute names (where supported) |
-p | Show project number (if enabled) |
-v | Show file version/generation (if enabled) |
Example
lsattr /var/www/example.com/public_html/wp-config.php
Example output
----i--------e----- /var/www/example.com/public_html/wp-config.php
The i flag indicates the file is immutable.
chattr: change attributes
Syntax
chattr [+|-|=][FLAGS] [files...]
Common flags (ext4)
| Flag | Meaning |
|---|---|
i | Immutable (cannot modify, delete, rename, or link until removed) |
a | Append-only (file can only be opened for appending) |
A | Do not update atime |
S | Synchronous updates |
d | Exclude from dump |
e | Extents (usually default) |
j | Data journaling |
t | No tail-merging |
u | Allow undelete (historical; limited usefulness) |
E, I, P, T | Implementation/project-specific flags (rarely used on WP hosts) |
Options
| Option | Meaning |
|---|---|
-R | Recurse into directories |
-V | Verbose |
Examples
sudo chattr +i /var/www/example.com/public_html/wp-config.php
sudo chattr -R +i /var/www/example.com/public_html/wp-content/themes/my-theme
sudo chattr -R -i /var/www/example.com/public_html/wp-content/themes/my-theme
For updates and deploys, remove +i, perform the change, validate, then re-apply +i.
Extended Attributes (xattrs)
getfattr: read xattrs
Syntax
getfattr [OPTIONS] PATH...
Key options
| Option | Meaning |
|---|---|
-n <name> | Show only a specific attribute |
-d | Dump attribute names and values |
-m <regex> | Filter attribute names (default matches user..*) |
--only-values | Print only the value |
-e <enc> | Value encoding: text or hex |
-R | Recurse into directories |
-h | Act on the symlink itself |
-L | Follow symlinks |
-P | Do not follow symlinks |
Examples
getfattr -d /var/www/example.com/public_html/wp-config.php
Expected (if none set):
# file: /var/www/example.com/public_html/wp-config.php
getfattr -n user.comment /path/to/file
getfattr --only-values -n user.comment /path/to/file
setfattr: write/remove xattrs
Syntax
setfattr [OPTIONS] PATH...
Key options
| Option | Meaning |
|---|---|
-n <name> | Attribute name (example: user.comment) |
-v <value> | Attribute value |
-x <name> | Remove the attribute |
-R | Recurse into directories |
-h | Act on the symlink itself |
-L / -P | Follow / do not follow symlinks |
Examples
sudo setfattr -n user.comment -v "Frozen after audit 2025-10-31"
/var/www/example.com/public_html/wp-content/themes/my-theme
sudo setfattr -x user.comment
/var/www/example.com/public_html/wp-content/themes/my-theme
sudo setfattr -R -n user.release -v "2025.10.31"
/var/www/example.com/public_html/wp-content/plugins
Namespaces you will see
| Namespace | Typical use |
|---|---|
user.* | Admin scripts and operational metadata |
security.* | Security frameworks and capabilities |
system.* | Kernel and low-level uses |
trusted.* | Admin-only metadata (often requires CAP_SYS_ADMIN) |
Linux Capabilities (stored as xattr)
setcap / getcap
Syntax
sudo setcap <caps> <path-to-binary>
getcap -r <dir>
Capabilities are stored in the security.capability extended attribute.
Examples (use only when you understand the impact)
Allow a non-root binary to bind to ports < 1024:
sudo setcap 'cap_net_bind_service=+ep' /usr/sbin/myproxy
getcap /usr/sbin/myproxy
Expected:
/usr/sbin/myproxy = cap_net_bind_service+ep
Remove capabilities:
sudo setcap -r /usr/sbin/myproxy
Avoid setting capabilities on core web stack binaries (PHP-FPM, OpenLiteSpeed, Apache, Nginx) unless you have a specific, audited requirement.
Preserve Metadata in Copies and Backups
cp
cp --preserve=xattr -a SRC DEST
rsync
rsync -aAXH SRC/ DEST/
tar
tar --xattrs --acls -cpf backup.tar /var/www/example.com
tar --xattrs --acls -xpf backup.tar -C /
If you restore over files or directories marked immutable, remove the flag first (sudo chattr -R -i PATH), restore, then re-apply the flag.
WordPress-Focused Hardening Patterns
Lock down wp-config.php (and .htaccess if used)
sudo chattr +i /var/www/example.com/public_html/wp-config.php
sudo chattr +i /var/www/example.com/public_html/.htaccess
Freeze theme/plugin code for production
sudo chattr -R +i /var/www/example.com/public_html/wp-content/themes/my-theme
sudo chattr -R +i /var/www/example.com/public_html/wp-content/plugins/critical-plugin
Append-only on a log file (when applicable)
sudo chattr +a /path/to/webroot/logs/access.log
Provenance tags via xattrs
Tag a deploy:
sudo setfattr -R -n user.release -v "2025.10.31" /var/www/example.com/public_html
Audit:
getfattr -R -m 'user.release' -d /var/www/example.com/public_html | less
Production Workflow (safe and reversible)
chattr -R can lock large trees and break updates and restores if you forget to remove +i. Apply it narrowly and keep a rollback command ready.
- Survey current state.
lsattr -R /var/www/example.com/public_html | grep -E ' i | a ' || true
getfattr -R -m 'user..*' -d /var/www/example.com/public_html | less
- Baseline locks.
sudo chattr +i /var/www/example.com/public_html/wp-config.php
test -f /var/www/example.com/public_html/.htaccess && sudo chattr +i /var/www/example.com/public_html/.htaccess || true
- Freeze code; keep uploads writable.
sudo chattr -R +i /var/www/example.com/public_html/wp-content/themes
sudo chattr -R +i /var/www/example.com/public_html/wp-content/plugins
sudo chattr -R -i /var/www/example.com/public_html/wp-content/uploads
- Tag the release.
sudo setfattr -R -n user.release -v "2025.10.31" /var/www/example.com/public_html
- Backups with metadata.
sudo chattr -R -i /var/www/example.com/public_html
sudo rsync -aAXH /var/www/example.com/public_html/ /backups/example.com_2025-10-31/
# Re-apply only the locks you actually want
sudo chattr +i /var/www/example.com/public_html/wp-config.php
test -f /var/www/example.com/public_html/.htaccess && sudo chattr +i /var/www/example.com/public_html/.htaccess || true
sudo chattr -R +i /var/www/example.com/public_html/wp-content/themes
sudo chattr -R +i /var/www/example.com/public_html/wp-content/plugins
sudo chattr -R -i /var/www/example.com/public_html/wp-content/uploads
- Document and monitor.
lsattr -R /var/www/example.com/public_html > /root/example.lsattr.txt
diff -u /root/example.lsattr.txt <(lsattr -R /var/www/example.com/public_html) || echo "Attribute drift detected"
The <( ... ) process substitution syntax requires bash.
Bulk Operations with find/xargs (and rollback)
Apply immutable to all .php under wp-includes/:
find /var/www/example.com/public_html/wp-includes -type f -name '*.php' -print0
| sudo xargs -0 chattr +i
Remove immutable (rollback):
find /var/www/example.com/public_html/wp-includes -type f -name '*.php' -print0
| sudo xargs -0 chattr -i
Add an xattr marker to all plugin files:
find /var/www/example.com/public_html/wp-content/plugins -type f -print0
| sudo xargs -0 -I{} setfattr -n user.release -v "2025.10.31" {}
Use -print0 with xargs -0 to safely handle paths containing spaces and newlines.
Best Practices
- Keep immutable scope minimal (config and code you truly want frozen).
- Standardize deploy steps: remove
+i-> deploy -> test -> re-apply+i. - Preserve metadata in backups (
rsync -aAXH,tar --xattrs --acls). - Avoid capabilities unless you have a specific, justified need.
- Snapshot attribute state (
lsattr -R) and periodically check drift.
Troubleshooting Matrix
| Symptom | Probable cause | Check | Fix |
|---|---|---|---|
Permission denied when editing wp-config.php (even as root) | +i immutable flag | lsattr wp-config.php shows i | sudo chattr -i wp-config.php, edit, then re-apply sudo chattr +i wp-config.php |
| Plugin/theme not updating | Parent dir or files are immutable | lsattr -R wp-content/plugins | Temporarily remove -i on the target, update, then re-apply +i |
| Backup/restore misses metadata | Backup tool not preserving xattrs/ACLs | Review backup command | Use rsync -aAXH or tar --xattrs --acls |
setfattr fails with Operation not supported | Filesystem/mount options do not support xattrs | Check filesystem type and mount options | Use a filesystem and mount options that support xattrs |
| Capabilities do not persist | Binary is on a filesystem that does not support xattrs/caps | getcap <bin> returns nothing after setcap | Move binary to a suitable filesystem and retry |
Quick Lab (Hands-On, Safe)
- Create a sandbox file and make it immutable.
cd /tmp
echo "hello" > demo.txt
sudo chattr +i demo.txt
echo "world" >> demo.txt # should fail
- Inspect the attribute flags.
lsattr demo.txt
- Remove the flag, edit, and add an xattr.
sudo chattr -i demo.txt
echo "world" >> demo.txt
sudo setfattr -n user.comment -v "lab-note" demo.txt
getfattr -d demo.txt
- Clean up.
rm demo.txt
Expected highlights:
- Append fails when
+iis set. getfattr -dshowsuser.comment="lab-note".
Cheat Sheet (Copy-Ready)
- List attributes:
lsattr [-R] [-adlpv] PATH - Make immutable:
sudo chattr +i FILE|DIR - Remove immutable:
sudo chattr -i FILE|DIR - Make append-only:
sudo chattr +a FILE - List xattrs:
getfattr -d [-m REGEX] PATH - Get one xattr:
getfattr -n user.comment PATH - Set xattr:
sudo setfattr -n user.comment -v "TEXT" PATH - Remove xattr:
sudo setfattr -x user.comment PATH - Grant capability:
sudo setcap 'cap_net_bind_service=+ep' /path/to/bin - List capabilities:
getcap -r / - Rsync with metadata:
rsync -aAXH SRC/ DEST/ - Tar with xattrs/ACLs:
tar --xattrs --acls -cpf backup.tar DIR
Mini-Quiz (Self-Check)
- What is the difference between xattrs and filesystem attributes?
- Why can root fail to edit an immutable file?
- Which rsync flags preserve xattrs and ACLs?
- What namespace do POSIX capabilities live in?
- What is a safe sequence to update a theme that is currently immutable?
Answers (brief):
- xattrs are key-value metadata; filesystem attributes are filesystem flags like immutable.
- The
+iflag blocks modifications until it is removed. -Xpreserves xattrs and-Apreserves ACLs (commonly-aAXH).security.capability.- Remove
-i-> update -> test -> re-apply+i.
Appendix: Advanced Notes
- ext4 on Ubuntu typically has xattrs enabled by default; verify your filesystem and mount options if
setfattrfails. - Ubuntu commonly uses AppArmor; SELinux labels are also xattrs (
security.selinux) but are usually not active unless you enable SELinux. - Moving files across filesystems can drop xattrs/attributes; use tools that preserve them.
- xattrs and attribute flags complement (not replace) a sound permissions and ACL strategy.