RHEL Basics #6: Filesystem Basics — XFS, mount, /etc/fstab
The files we set permissions on in #5 live on a filesystem, which sits on a block device (a disk), mounted somewhere in the directory tree. This post covers that layer. By the end, we’ll have added a fresh disk and mounted it as a data directory.
Where this post sits in the RHEL Basics series:
- #1 What is RHEL — From Fedora to RHEL, plus AlmaLinux and Rocky Linux
- #2 Setup — Installing RHEL 9, Subscription Manager, first login
- #3 dnf and package management — repo, modules, AppStream
- #4 Intro to systemd — services, targets, journalctl
- #5 Users / groups / permissions — UID/GID, sudo, ACL
- #6 Filesystem basics — XFS, mount, /etc/fstab ← this post
- #7 Basic security — firewalld, SSH hardening
XFS — the RHEL 9 default #
RHEL 9’s default filesystem is XFS (default since RHEL 7; before that, ext4).
$ findmnt -t xfs,ext4
TARGET SOURCE FSTYPE OPTIONS
/ /dev/mapper/rhel-root xfs rw,relatime,attr2,inode64,...
/boot /dev/vda1 xfs rw,relatime,attr2,inode64,...
/home /dev/mapper/rhel-home xfs rw,relatime,attr2,inode64,...XFS vs ext4 #
| Item | XFS | ext4 |
|---|---|---|
| RHEL default | ✅ (RHEL 7+) | RHEL 6 era default |
| Large files / large volumes | Excellent (petabytes) | Good (tens of TB) |
| Lots of small files / heavy metadata churn | Average | Slightly better |
Shrink (resize smaller) | Not supported | Supported |
Grow (resize larger) | Yes (xfs_growfs) | Yes (resize2fs) |
| Snapshots | Through LVM (external) | Through LVM (external) |
The most-quoted difference is no shrinking on XFS. Once you size a volume, you can only grow it. Rarely a problem in practice, but worth knowing.
Other filesystems you’ll see — btrfs was a tech preview on RHEL 7 and officially deprecated from RHEL 8. Don’t use it. Stratis stepped in as the replacement (Intermediate #3). For container workloads you’ll see overlayfs, but that belongs to Docker / Podman’s #4 Volumes and networks.
Looking at disks — lsblk / df / du
#
lsblk — block device tree
#
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 252:0 0 40G 0 disk
├─vda1 252:1 0 1G 0 part /boot
└─vda2 252:2 0 39G 0 part
├─rhel-root 253:0 0 25G 0 lvm /
├─rhel-swap 253:1 0 4G 0 lvm [SWAP]
└─rhel-home 253:2 0 10G 0 lvm /home
vdb 252:16 0 20G 0 disk ← newly attached diskvda is the system disk; vdb is an empty disk we attached for later. In virtualized environments names like vda / vdb are common; on bare metal or older virtualization, sda / sdb show up more.
df — usage of mounted filesystems
#
$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/rhel-root xfs 25G 12G 13G 48% /
/dev/vda1 xfs 974M 254M 720M 27% /boot
/dev/mapper/rhel-home xfs 10G 1.2G 8.8G 13% /home
tmpfs tmpfs 1.8G 12M 1.8G 1% /run-h is human-readable units, -T shows filesystem types. When you get a disk-usage alert in production, this is usually the first command.
du — usage by directory
#
$ du -sh /var/log # total of /var/log only
$ du -sh /var/* | sort -h # each subdirectory under /var, sorted
$ du -sh /var/log/* | sort -h | tail -10 # 10 biggestWhen / fills up, the typical flow is du -sh /* and then drilling into the heavy directory.
Adding a new disk #
Now the hands-on part. Take the /dev/vdb we saw with lsblk and mount it whole at /data.
Adding a disk to your VM — UTM: stop the VM, then Edit → New → add a Virtual Drive. VirtualBox: VM Settings → Storage → Controller: SATA → Add Hard Disk.
1) Confirm the disk is recognized #
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 252:0 0 40G 0 disk
...
vdb 252:16 0 20G 0 disk ← empty disk2) Create a partition — parted or gdisk
#
A small disk can stay as one big partition. Disks ≥ 2TB require GPT. parted handles both GPT and MBR cleanly.
$ sudo parted /dev/vdb mklabel gpt
$ sudo parted /dev/vdb mkpart data xfs 0% 100%
$ sudo parted /dev/vdb print
Model: Virtio Block Device (virtblk)
Disk /dev/vdb: 21.5GB
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 1049kB 21.5GB 21.5GB dataA new partition /dev/vdb1 is created (vdb + 1).
$ lsblk /dev/vdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vdb 252:16 0 20G 0 disk
└─vdb1 252:17 0 20G 0 partYou can also format the whole disk without a partition (
mkfs.xfs /dev/vdbdirectly). The recommended path is to keep a partition table — other tools recognize it more cleanly.
3) Make a filesystem — mkfs.xfs
#
$ sudo mkfs.xfs /dev/vdb1
meta-data=/dev/vdb1 isize=512 agcount=4, agsize=...
data = bsize=4096 blocks=...
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=...
realtime =none extsz=4096 blocks=0, rtextents=0
Discarding blocks...Done.For ext4: sudo mkfs.ext4 /dev/vdb1. Different output, same flow.
4) Make a mount point and temp-mount #
$ sudo mkdir -p /data
$ sudo mount /dev/vdb1 /data
$ df -hT /data
Filesystem Type Size Used Avail Use% Mounted on
/dev/vdb1 xfs 20G 175M 20G 1% /data/data works at this point — but it disappears after reboot. That’s a temporary mount. The next step makes it permanent.
5) Permanent registration in /etc/fstab — by UUID
#
/etc/fstab is the list of filesystems to auto-mount on boot.
$ sudo blkid /dev/vdb1
/dev/vdb1: UUID="abc12345-67de-89fa-bcde-1234567890ab" TYPE="xfs" PARTUUID="..."Copy the UUID and add a line to /etc/fstab:
UUID=abc12345-67de-89fa-bcde-1234567890ab /data xfs defaults 0 0Six columns:
| # | Meaning |
|---|---|
| 1 | Device — UUID is preferred (device names can change between boots) |
| 2 | Mount point |
| 3 | Filesystem type |
| 4 | Options — defaults, noatime, nofail, etc. |
| 5 | dump backup flag (almost always 0) |
| 6 | fsck order — / is 1, others 2 or 0 |
6) Verify with mount -a
#
A typo in /etc/fstab can block boot, so always verify before rebooting.
$ sudo umount /data # drop the temp mount
$ sudo mount -a # try every entry in /etc/fstab
$ df -hT /data
/dev/vdb1 xfs 20G 175M 20G 1% /data ← back via /etc/fstabIf mount -a finishes without errors, you’re safe. If something errors out, fix the line and run again.
Watch out — booting with a broken
/etc/fstabhalts at the mount stage and drops you into emergency mode (emergency.target). On RHEL 9, addingnofaillets boot continue even if a particular entry fails — recommended on data disks.
UUID=abc12345-... /data xfs defaults,nofail 0 0Common /etc/fstab options
#
| Option | Meaning |
|---|---|
defaults | rw, suid, dev, exec, auto, nouser, async (default bundle) |
noatime | Don’t update access time on read — faster, recommended |
nofail | Continue boot if this entry fails (use on data disks) |
ro / rw | Read-only / read-write |
noexec | Block executing binaries here — security on /tmp, etc. |
nosuid | Ignore SUID bits — security |
nodev | Ignore device files — security |
_netdev | Network filesystem (NFS, iSCSI) — wait for network |
For data disks in production, the standard is defaults,noatime,nofail. Add nosuid,nodev,noexec when security matters.
swap #
Current swap #
$ swapon --show
NAME TYPE SIZE USED PRIO
/dev/mapper/rhel-swap partition 4G 12M -2
$ free -h
total used free ...
Mem: 3.7Gi 1.2Gi 1.8Gi
Swap: 4.0Gi 12Mi 4.0GiAdding a swap file #
When there’s no partition space, you can use a file as swap. Common for temporary or extra swap.
$ sudo dd if=/dev/zero of=/swapfile bs=1M count=1024 status=progress
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ swapon --show
NAME TYPE SIZE USED PRIO
...
/swapfile file 1024M 0B -3To make it permanent, add to /etc/fstab:
/swapfile none swap sw 0 0swappiness
#
How aggressively the kernel uses swap instead of RAM, on a scale of 0–100. Default is 60.
$ cat /proc/sys/vm/swappiness
60
$ sudo sysctl vm.swappiness=10 # immediate
$ echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf # persistentServers usually set it lower (10–30). Heavy swap usage tends to slow response times noticeably.
A glance at LVM #
The auto-partitioning from #2 resulted in LVM (/dev/mapper/rhel-root). LVM is an abstraction layer between physical disks and the filesystem.
PV (Physical Volume) ─┐
PV ────────────────────┤ → VG (Volume Group) → LV (Logical Volume) → filesystem
PV ────────────────────┘The three layers:
- PV — mark a disk or partition for LVM use
- VG — a pool of PVs
- LV — a logical volume carved from a VG. The filesystem sits here
LVM’s real value is growing, shrinking, and snapshots. If /home fills up, add a PV to the same VG and grow the LV. The deep dive lives in Intermediate #2 LVM.
The /dev/vdb1 we created here was a plain partition for simplicity, but in production putting filesystems on LVM is essentially the standard.
Common pitfalls #
“After reboot, the disk isn’t mounted” #
You forgot to register it in /etc/fstab. The bare mount command only affects the running system and is lost on reboot. Add a line to /etc/fstab and verify with mount -a — that’s the answer.
“I broke /etc/fstab and the system won’t boot”
#
Boot rescue mode from the GRUB menu and edit /etc/fstab directly. As prevention, add nofail. And always run mount -a before rebooting a production box.
“The device name changed” #
/dev/sda becomes /dev/sdb after a reboot. SATA slot changes, USB ordering, or how disks were attached in virtualization. That’s why UUID is the recommended way — blkid, then /etc/fstab.
“df and du don’t agree” #
df reads the filesystem metadata; du walks the actual files. If a process still holds a deleted file open, df counts it as used while du doesn’t see it. lsof | grep deleted finds those processes — restarting them frees the space.
“I want to shrink XFS” #
You can’t. The path is back up the data → make a smaller filesystem → restore. Even on LVM, the LV can shrink, but XFS inside it can’t.
Common commands at a glance #
| Command | What it does |
|---|---|
lsblk [-f] | Block device tree (+ filesystem / UUID) |
blkid <device> | UUID and filesystem type |
findmnt [-t xfs] | Mount tree |
df -hT [path] | Filesystem usage |
du -sh <path> / du -sh /path/* | sort -h | Directory usage |
parted /dev/X mklabel gpt / mkpart NAME xfs 0% 100% | Partitioning |
mkfs.xfs /dev/X / mkfs.ext4 /dev/X | Format |
mount /dev/X /mnt / umount /mnt | Temporary mount |
mount -a | Try every /etc/fstab entry |
swapon --show / swapoff /path | swap status / off |
mkswap / swapon | Create and enable swap |
xfs_growfs /mnt / resize2fs /dev/X | Grow (after extending the LV) |
Wrap-up #
The picture from this post:
- The default fs on RHEL 9 is XFS — strong on big volumes, but can’t shrink.
lsblk(block tree) /df(mounted fs usage) /du(per directory) — the disk-diagnosis trio.- New disk flow: recognize →
parted→mkfs.xfs→mount→ register in/etc/fstab→ verify withmount -a. - Register
/etc/fstabentries by UUID (device names can change). Usenofailfor data disks. - Swap as a partition or a file.
swappinessis usually 10–30 on servers. - In production, put filesystems on LVM — grow / snapshots are standard (Intermediate #2).
Next — basic security #
With the filesystem in place, data lives on top of it, and the paths external traffic takes are the firewall and SSH. The last post and the convergence point of the series.
#7 Basic security — firewalld, SSH hardening covers RHEL’s firewall abstraction firewalld and its zone model, the firewall-cmd family (permanent vs runtime), common zone/service/port patterns, and SSH hardening — disabling password auth, key-only auth, locking down root login, port changes, splitting sshd_config.d/ — all in one final post.