Red Hat · linux

Red Hat RHCSA EX200

Master essential Linux system administration on RHEL 9 — users, permissions, storage, networking, firewall, SELinux, systemd, containers, and the boot process. Hands-on, performance-based exam preparation.

11Modules
35 hoursDuration
intermediateLevel
EX200Exam code
2.5 hoursDuration
210 / 300Passing score
$400Exam fee (USD)
3 yearsValidity
Performance-basedFormat
Study on the go — CertQuests Podcast

Sysadmin commands, LVM walkthroughs, and SELinux troubleshooting drills while commuting or working out. New episodes covering RHCSA EX200 objectives drop weekly.

▶ Listen on Spotify

Why earn the RHCSA?

EX200 is the floor cert for serious Linux work. It's a fully hands-on, performance-based exam — you sit in front of two RHEL 9 VMs in a kiosk and solve real tasks. No multiple choice, no notes, no internet. Passing it proves you can actually run a Linux system.

  • Hands-on, performance-based — the certification that actually proves CLI fluency, not memorisation
  • Foundation for the entire Red Hat ladder: RHCE EX294 (Ansible), RHCA Specialist tracks, and OpenShift certifications
  • Prerequisite or fast-track signal for many SRE, platform-engineer, and Linux-ops roles in Red Hat-aligned shops
  • Validates real CLI skill: nmcli, firewalld, semanage, LVM, systemd — not abstract architecture
  • Performance-based format means no question banks to memorise — the test is the work itself
  • Strong salary signal — ~€55–80k starting in EU markets, $90–110k median in the US for RHCSA-certified Linux engineers
Exam strategy: EX200 puts you in a kiosk with two RHEL 9 VMs, no notes, no internet, and 2.5 hours of clock. Practice on RHEL 9, Rocky Linux 9, or AlmaLinux 9 in a lab — same kernel, same tools. Time-box every task to ~6 minutes; if you're stuck, move on and circle back. Always save fstab and GRUB edits, then test with mount -a and a reboot — broken persistent config is the #1 way candidates lose points. SELinux must stay enforcing.

RHCSA exam objectives

Red Hat doesn't publish explicit percentages, but task-cluster weight on past exams concentrates heavily on storage, users + permissions + SELinux, and systemd. Containers is the newest objective and growing.

Storage & filesystems 22%
Users, permissions, SELinux 20%
Boot, services, scheduled tasks 18%
Networking & firewall 16%
Basic system tools + package management 14%
Containers (Podman) & automation 10%

11 modules · ~35 hours

Each module maps to a cluster of RHCSA objectives. Work through them in order, or jump to the area where you're weakest. Every lesson ends with a mini-quiz CTA so you can drill recall before moving on.

01

Essential Linux Commands3 lessons

The CLI is the entire surface of the RHCSA. Every minute of the exam is spent typing, so shell fluency directly translates into points. Master Bash quoting and redirection, file navigation with find and locate, archives with tar and star, and the text-processing trio grep/sed/awk. Build muscle memory on tab completion, history search, and stream redirection until you don't think about them.

bash redirection pipes find-locate tar-star grep-sed-awk hard-soft-links history
~3h
📖 Read in-depth chapter
The CLI is the exam — every minute is spent typing. Build reflex around Ctrl+R history search, &> file redirection, and the grep / sed / awk trio. Master find with combined criteria, tar with the right compression flag, and the hard-vs-soft-link distinction cold — these are reflex skills the timer rewards.
Lesson 1.1 Shell basics — Bash, redirection, history

Every RHCSA task is typed into a Bash prompt. The exam rewards speed: tab completion, history search, and clean stream redirection are the difference between finishing and timing out.

Key concepts
  • Bash shell fundamentals: Bash is the default shell on RHEL. Commands follow the structure command [options] [arguments]. Use man command for documentation, info command for detailed manuals, and command --help for quick usage summaries.
  • Command history & editing: Use the up/down arrows to cycle through history, history to list all previous commands, !n to re-run command number n, and Ctrl+R to reverse-search history. Tab completion finishes commands, file names, and paths automatically.
  • Input/output redirection: > redirects stdout and overwrites the file, >> appends. 2> redirects stderr, &> redirects both stdout and stderr. < feeds a file as stdin. These operators are essential for scripting and log capture.
  • Piping & command chaining: The pipe | sends stdout of one command as stdin to the next (e.g., ps aux | grep httpd). Chain commands with && (run next only if previous succeeds), || (run next only if previous fails), or ; (run sequentially regardless).
  • Shell variables & environment: Set variables with VAR=value, export them with export VAR. Key environment variables include PATH, HOME, USER, SHELL, and PS1. Use env or printenv to list all environment variables.
Concrete example

Task: capture both stdout and stderr from a long-running dnf update while watching the output live. Solution: tee with combined streamsdnf -y update 2>&1 | tee /var/log/dnf-update.log. The 2>&1 merges stderr into stdout before the pipe; tee writes to file and stdout simultaneously. Verify the log with tail -f /var/log/dnf-update.log in another session. For a quick one-shot capture without live view, use dnf -y update &> /var/log/dnf-update.log.

Key takeaway: The shell is the exam. Master tab completion, Ctrl+R history search, and &> file.log redirection — they save minutes per task.
⚡ Mini-quiz
Drill shell redirection and history scenarios → study mode (10 questions).
Lesson 1.2 File management — copy, find, archive, link

File operations are the spine of every exam task. The exam tests find with combined criteria, tar with the right compression flag, and the difference between hard and soft links cold.

Key concepts
  • Core file operations: ls -la lists files with permissions and hidden entries. cp -r copies directories recursively. mv moves or renames. rm -rf removes recursively and forcefully. mkdir -p creates nested directories in one command.
  • Finding files: find / -name "*.conf" -type f searches the filesystem in real time by name, type, size, permissions, or modification time. locate filename queries a pre-built database (updated via updatedb) for faster but potentially stale results.
  • Archiving & compression: tar -czf archive.tar.gz /path creates a gzip-compressed archive. tar -xzf archive.tar.gz extracts it. tar -cjf uses bzip2, tar -cJf uses xz. star is an alternative archiver that preserves extended attributes and SELinux contexts.
  • Hard & soft links: Hard links (ln file link) share the same inode and data blocks — deleting the original does not affect the link. Soft links (ln -s target link) are pointers to a path — if the target is deleted, the symlink breaks. Hard links cannot cross filesystems or link to directories.
  • File metadata: stat file shows inode number, size, permissions, timestamps (access, modify, change). file filename identifies file type. du -sh /path shows disk usage for a directory, and df -h shows filesystem disk space usage.
Concrete example

Task: find every .conf file under /etc owned by root, larger than 1KB, modified in the last 7 days, then archive them to /root/etc-backup.tar.gz preserving SELinux contexts. Solution: combine find with starfind /etc -name "*.conf" -type f -user root -size +1k -mtime -7 > /tmp/list.txt, then star -c -xattr -H=exustar -f /root/etc-backup.tar.gz list=/tmp/list.txt. Verify contexts survived with star -tv -xattr -f /root/etc-backup.tar.gz | head.

Key takeaway: find with combined criteria for selection, star when SELinux contexts must survive an archive round-trip. Hard links don't cross filesystems; soft links break when the target moves.
⚡ Mini-quiz
Practise file-management scenarios → quick quiz (5 questions).
Lesson 1.3 Text processing — grep, sed, awk, and friends

Many RHCSA tasks reduce to "extract this from a config, transform it, write it back". The exam tests grep patterns, sed in-place edits, and awk field extraction often enough that hesitation costs minutes.

Key concepts
  • Grep & regular expressions: grep -i pattern file searches case-insensitively. grep -r pattern /dir searches recursively. grep -E "regex" enables extended regex. Basic patterns: ^ (start of line), $ (end of line), .* (any characters), [a-z] (character class).
  • Sed & awk: sed 's/old/new/g' file performs global search-and-replace. sed -i edits files in place. awk '{print $1, $3}' file extracts specific fields from structured text. Both are essential for automated configuration changes in scripts.
  • Text filtering tools: cut -d: -f1 /etc/passwd extracts the first field (usernames). sort orders lines alphabetically or numerically (-n). uniq removes adjacent duplicates (pipe from sort first). wc -l counts lines in a file.
  • File viewing: head -n 20 file shows the first 20 lines. tail -n 20 file shows the last 20. tail -f /var/log/messages follows a log file in real time. less provides paginated viewing with search (press /pattern to search forward).
  • Comparing files: diff file1 file2 shows line-by-line differences. diff -u produces unified format output commonly used in patches. comm compares two sorted files and shows lines unique to each or common to both.
Concrete example

Task: from /etc/passwd, list usernames with UID ≥ 1000 (regular users), sorted alphabetically. Solution: awk + sortawk -F: '$3 >= 1000 {print $1}' /etc/passwd | sort. Variant: count them with ... | wc -l. To make a backup-then-edit pattern stick, sed -i.bak 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config — the .bak suffix writes a safety copy before mutating.

Key takeaway: grep -v "^#" | grep -v "^$" to strip comments and blanks; sed -i.bak for safe in-place edits; awk -F: '{print $1}' for fast field extraction.
⚡ Mini-quiz
Drill grep / sed / awk scenarios → study mode (10 questions).
💻 Worked scenario — Audit accounts and lock anomalies
Task: list non-system accounts on a fresh server, find those with no real shell or no home, and disable them. Sequence: (1) awk -F: '$3>=1000 {print $1":"$6":"$7}' /etc/passwd — list real users with their home + shell; (2) pipe through awk -F: '$3=="" || $3=="/sbin/nologin" {print $1}' to flag dead accounts; (3) for each flagged user, usermod -L user to lock the password and chage -E 0 user to expire the account. Verify: passwd -S user shows L (locked); chage -l user shows account expired.
Key takeaways
  • Tab completion, Ctrl+R, and !! save minutes per task — drill them until they're muscle memory before the exam.
  • Redirect stdout + stderr with &> file or 2>&1 | tee; chain with &&, ||, and pipes to compose tools on the fly.
  • Hard links share an inode (same data, no cross-fs); soft links are pointers (can cross filesystems but break if the target moves) — the exam tests the difference.
⚡ Mini-quiz — Drill shell redirection, find options, tar flags, and link types.
Quick quiz →
02

File Systems & Storage3 lessons

The single heaviest objective cluster. Expect to partition a fresh disk, build LVM on top, format with XFS or ext4, persist the mount in /etc/fstab by UUID, and extend the volume online. XFS is the RHEL default; LVM is the layer that makes growth painless. Broken fstab entries are the #1 way candidates fail — always mount -a before rebooting.

parted fdisk-gdisk xfs ext4 lvm fstab blkid-uuid swap
~5h
📖 Read in-depth chapter
The single heaviest objective cluster — expect a fresh disk, an LVM build, an XFS or ext4 filesystem, a UUID-based /etc/fstab entry, and an online extend. XFS is the RHEL default; LVM is the layer that makes growth painless. A broken /etc/fstab entry is the #1 way candidates lock themselves out of the grading VM, so always validate with mount -a before rebooting.
Lesson 2.1 Disk partitioning — MBR, GPT, parted

Before a filesystem, before LVM, there's a partition. The exam will hand you a fresh virtual disk and expect you to slice it correctly the first time.

Key concepts
  • MBR vs GPT: MBR (Master Boot Record) supports up to 4 primary partitions and disks up to 2 TB. GPT (GUID Partition Table) supports 128 partitions and disks larger than 2 TB. UEFI systems require GPT; BIOS systems traditionally use MBR.
  • Partitioning with fdisk: fdisk /dev/sdb opens the interactive MBR partitioning tool. Use n to create, d to delete, t to change type (83 for Linux, 8e for LVM), p to print, and w to write changes. Run partprobe to inform the kernel of changes.
  • Partitioning with gdisk: gdisk /dev/sdb is the GPT equivalent of fdisk. Supports the same workflow but with GPT partition types (8300 for Linux filesystem, 8e00 for LVM). Use sgdisk for scriptable non-interactive GPT partitioning.
  • Parted: parted /dev/sdb supports both MBR and GPT. Use mklabel gpt to initialize, mkpart primary ext4 1MiB 500MiB to create partitions with precise sizing. Parted makes changes immediately without a write step.
  • Partition types: Primary partitions are bootable and limited to 4 per MBR disk. Extended partitions act as containers for logical partitions, allowing more than 4 partitions on MBR. Linux LVM type (8e) marks partitions for use as physical volumes.
Concrete example

Task: take a fresh 10 GB disk /dev/sdb, label it GPT, carve a 4 GB partition for LVM use. Solution: parted non-interactiveparted /dev/sdb mklabel gpt, parted /dev/sdb mkpart primary 1MiB 4097MiB, parted /dev/sdb set 1 lvm on. Verify with lsblk and parted /dev/sdb print. If using fdisk or gdisk remember to press w to write — and run partprobe after if the kernel still shows the old table.

Key takeaway: parted writes immediately (no undo); fdisk/gdisk need w. Always lsblk or fdisk -l first to identify the right device. partprobe if the kernel hasn't caught up.
⚡ Mini-quiz
Drill partitioning scenarios → study mode (10 questions).
Lesson 2.2 File systems — XFS, ext4, fstab, swap

Every storage task on the exam ends with "make it survive a reboot". That means a correct /etc/fstab entry by UUID, tested with mount -a before you ever reboot.

Key concepts
  • ext4 vs XFS: ext4 supports shrinking and growing, has a maximum file size of 16 TB, and uses the resize2fs command. XFS is the default on RHEL, supports only growing (no shrink), has a maximum file size of 8 EB, and uses xfs_growfs. Both support journaling.
  • Creating file systems: mkfs.xfs /dev/sdb1 creates an XFS filesystem. mkfs.ext4 /dev/sdb1 creates ext4. Use -L label to assign a label. blkid displays the UUID, label, and filesystem type of block devices.
  • Mounting filesystems: mount /dev/sdb1 /mnt/data mounts temporarily. For persistent mounts, add an entry to /etc/fstab with the format: UUID=xxxx /mount/point xfs defaults 0 0. Run mount -a to test fstab entries without rebooting.
  • UUID and labels: Always use UUIDs in /etc/fstab instead of device names like /dev/sdb1, because device names can change between boots. Use blkid to find UUIDs and xfs_admin -L label /dev/sdb1 or tune2fs -L label /dev/sdb1 to set labels.
  • Swap space: Create swap partitions with mkswap /dev/sdb2 and activate with swapon /dev/sdb2. Add to /etc/fstab as UUID=xxxx swap swap defaults 0 0. Use swapon --show to verify active swap devices.
Concrete example

Task: format /dev/sdb1 as XFS labelled data, mount persistently at /srv/data. Solution: format + UUID-based fstabmkfs.xfs -L data /dev/sdb1, capture the UUID with blkid /dev/sdb1, then append to /etc/fstab: UUID=<uuid> /srv/data xfs defaults 0 0. Critical validation: mkdir -p /srv/data && mount -a && df -h /srv/data. If mount -a errors, fix it now — a broken fstab can prevent the next boot.

Key takeaway: always UUIDs, never device names. mount -a before reboot. XFS can grow, never shrink — pick ext4 if the task says reduce.
⚡ Mini-quiz
Drill filesystem + fstab scenarios → quick quiz (5 questions).
Lesson 2.3 Logical Volume Manager — PVs, VGs, LVs, extension

LVM is the storage layer the exam tests most. Build it once, extend it twice — every RHCSA candidate should be able to walk PV → VG → LV → mkfs → mount → fstab without thinking.

Key concepts
  • LVM architecture: Physical Volumes (PVs) are partitions or whole disks initialized for LVM. Volume Groups (VGs) pool one or more PVs into a single storage pool. Logical Volumes (LVs) are carved from VGs and used like regular partitions for filesystems.
  • Creating LVM: pvcreate /dev/sdb1 initializes a PV. vgcreate myvg /dev/sdb1 /dev/sdc1 creates a VG from multiple PVs. lvcreate -n mylv -L 5G myvg creates a 5 GB LV. Then format with mkfs.xfs /dev/myvg/mylv and mount it.
  • Extending LVM: Add a new PV to the VG with vgextend myvg /dev/sdd1. Extend the LV with lvextend -L +2G /dev/myvg/mylv or use -l +100%FREE to use all remaining space. Then grow the filesystem: xfs_growfs /mount/point for XFS or resize2fs /dev/myvg/mylv for ext4.
  • Reducing LVM: Only ext4 supports shrinking. Unmount first, then e2fsck -f /dev/myvg/mylv, resize2fs /dev/myvg/mylv 3G, and finally lvreduce -L 3G /dev/myvg/mylv. XFS volumes cannot be reduced — only recreated.
  • LVM verification: pvs, vgs, lvs provide concise summaries. pvdisplay, vgdisplay, lvdisplay show detailed information. lsblk shows the block device hierarchy including LVM mappings.
Concrete example

Task: build a 6 GB XFS volume from two 4 GB PVs, then extend by 2 GB later. Solution: full lifecyclepvcreate /dev/sdb1 /dev/sdc1, vgcreate datavg /dev/sdb1 /dev/sdc1, lvcreate -n datalv -L 6G datavg, mkfs.xfs /dev/datavg/datalv, mount + fstab. To extend: lvextend -r -L +2G /dev/datavg/datalv — the -r flag grows the LV AND the XFS filesystem in one shot. Verify with lvs and df -h.

Key takeaway: memorise pvcreate → vgcreate → lvcreate → mkfs → mount → fstab. lvextend -r grows volume + filesystem in one command. -l +100%FREE for "use everything left".
⚡ Mini-quiz
Drill LVM extension scenarios → study mode (10 questions).
💻 Worked scenario — Extend an LVM logical volume online
Task: /var on vg_root/lv_var is at 95% and a new 10 GB disk /dev/sdb has just been attached. Grow the FS without a reboot. Sequence: (1) parted /dev/sdb mklabel gpt mkpart primary 0% 100%, then pvcreate /dev/sdb1; (2) vgextend vg_root /dev/sdb1; (3) lvextend -L +5G /dev/vg_root/lv_var; (4) xfs_growfs /var (use resize2fs for ext4). Verify: df -h /var shows the new size; lvs confirms LV size; pvs shows the new PV in the VG.
Key takeaways
  • Build the stack bottom-up: parted partition → pvcreatevgcreatelvcreatemkfs.xfsblkid for the UUID → /etc/fstab.
  • XFS grows with xfs_growfs (mounted, online); ext4 grows with resize2fs. Both require lvextend first — pair the LV grow with the FS grow.
  • Always reference filesystems in /etc/fstab by UUID, not /dev/sdXN; run mount -a after editing to catch typos before reboot.
⚡ Mini-quiz — Drill LVM stack creation, XFS/ext4 grow commands, fstab UUID syntax, and swap activation.
Quick quiz →
03

Users & Groups3 lessons

Account lifecycle: create, modify, lock, age, delete. Group membership and SGID for collaborative directories. sudo and visudo for delegated root — including NOPASSWD and the %wheel shortcut. The most common exam mistake is usermod -G without -a, which silently wipes a user's other supplementary groups.

useradd usermod chage groupadd sgid sudoers visudo wheel
~3h
📖 Read in-depth chapter
Full account lifecycle: useradd / usermod / chage / userdel, plus group membership and SGID for collaborative directories. sudo via visudo for delegated root — including NOPASSWD and the %wheel shortcut. The classic exam pitfall: usermod -G group1,group2 without -a silently wipes a user's other supplementary groups. Always use -aG.
Lesson 3.1 User management — useradd, usermod, chage

Account tasks appear on every exam: create users with specific UIDs and shells, set password aging, lock accounts. Read the requirements precisely — exam graders match exact UID, shell, and group fields.

Key concepts
  • Creating users: useradd username creates a user with defaults (home directory, shell, UID). Common options: -u UID sets a specific UID, -s /sbin/nologin prevents interactive login, -d /home/custom sets a custom home directory, -e 2026-12-31 sets an expiration date.
  • Modifying & deleting users: usermod -aG groupname username adds a user to a supplementary group (-a appends; without it, all other groups are removed). usermod -L username locks an account. userdel -r username deletes the user and their home directory.
  • Password management: passwd username sets or changes a password. chage -l username lists password aging info. chage -M 90 username sets maximum password age to 90 days. chage -d 0 username forces a password change at next login.
  • User configuration files: /etc/passwd stores user accounts (username:x:UID:GID:comment:home:shell). /etc/shadow stores encrypted passwords and aging data. /etc/login.defs defines system-wide defaults for UID ranges, password aging, and home directory creation.
  • System users vs regular users: System users (UID below 1000 on RHEL) are created for services and daemons with useradd -r. They typically have /sbin/nologin as their shell and no home directory. Regular users start at UID 1000.
Concrete example

Task: create user deploy with UID 2001, shell /bin/bash, password forced to change at first login, and account expiry on 2026-12-31. Solution: useradd + chageuseradd -u 2001 -s /bin/bash -e 2026-12-31 deploy, passwd deploy (set initial), then chage -d 0 deploy to force change on first login. Verify with id deploy (UID + groups) and chage -l deploy (aging fields).

Key takeaway: always -a with usermod -G or you wipe supplementary groups. Verify everything with id and chage -l.
⚡ Mini-quiz
Drill user-management scenarios → study mode (10 questions).
Lesson 3.2 Group management & collaborative directories

Groups + SGID is the canonical RHCSA pattern for "team X must share a directory". Expect a task that explicitly tests group creation, membership, and SGID inheritance.

Key concepts
  • Creating & modifying groups: groupadd groupname creates a new group. groupadd -g 5000 groupname sets a specific GID. groupmod -n newname oldname renames a group. groupdel groupname deletes a group (fails if it is any user's primary group).
  • Primary vs supplementary groups: Each user has one primary group (set at creation, stored in /etc/passwd GID field) and zero or more supplementary groups (listed in /etc/group). Files are created with the owner's primary group. Use newgrp groupname to temporarily switch the effective primary group.
  • Group administration: gpasswd -a user group adds a user to a group. gpasswd -d user group removes a user from a group. gpasswd -A user group makes a user a group administrator who can manage group membership without root access.
  • Group configuration files: /etc/group stores group definitions (groupname:x:GID:members). /etc/gshadow stores group passwords and administrator lists. View effective group memberships with id username or groups username.
  • Collaborative directories: Set the SGID bit on a shared directory (chmod g+s /shared) so that new files inherit the directory's group rather than the creator's primary group. This is essential for team collaboration on shared project directories.
Concrete example

Task: alice, bob, carol must share /shared/team with read-write access, new files automatically owned by the team group. Solution: group + SGIDgroupadd team, usermod -aG team alice (and bob, carol), mkdir -p /shared/team, chgrp team /shared/team, chmod 2770 /shared/team. The leading 2 sets SGID; 770 is rwx for owner+group. Verify with ls -ld /shared/team — look for the s in the group execute slot.

Key takeaway: SGID directory + group ownership solves "shared team folder". Users must log out and back in for new supplementary groups to take effect.
⚡ Mini-quiz
Drill group + SGID scenarios → quick quiz (5 questions).
Lesson 3.3 sudo, visudo, and delegated root

Delegating root cleanly with sudo is a recurring RHCSA pattern. The exam may ask you to give a specific user passwordless access to a single command, or wire up the %wheel group. Never edit /etc/sudoers with anything except visudo.

Key concepts
  • visudo & sudoers syntax: Always use visudo (locks file + syntax-checks before saving). The sudoers line format: USER HOST=(RUNAS) COMMANDS — e.g. alice ALL=(ALL) ALL grants alice full root via password.
  • The %wheel shortcut: RHEL ships with %wheel ALL=(ALL) ALL already uncommented in /etc/sudoers. Adding a user to the wheel group (usermod -aG wheel alice) grants full sudo immediately — no edit needed.
  • NOPASSWD entries: bob ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart httpd lets bob run that one command without entering a password — useful for automation hooks. Always scope NOPASSWD as narrowly as possible.
  • Drop-in files in /etc/sudoers.d/: Instead of editing the main sudoers, create per-purpose files like /etc/sudoers.d/10-deploy and edit them with visudo -f /etc/sudoers.d/10-deploy. RHEL parses every file in that directory. Files with a . or ~ in the name are ignored.
  • Command aliases & logging: Cmnd_Alias WEBSVC = /usr/bin/systemctl start httpd, /usr/bin/systemctl stop httpd lets you group commands. All sudo invocations log to /var/log/secure with the target user and command — verify after delegating.
Concrete example

Task: user deploy must restart httpd without entering a password, but nothing else. Solution: scoped NOPASSWD in a drop-invisudo -f /etc/sudoers.d/10-deploy, add the line deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart httpd. Save (visudo syntax-checks on exit). Verify as deploy: sudo systemctl restart httpd succeeds with no prompt; sudo systemctl restart sshd fails. Check /var/log/secure for the audit entry.

Key takeaway: always visudo, never a plain editor. Prefer %wheel for full delegation, narrow NOPASSWD entries in /etc/sudoers.d/ for single-command automation.
⚡ Mini-quiz
Drill sudoers + delegation scenarios → study mode (10 questions).
💻 Worked scenario — Create a sudo-restricted deploy account
Task: deploy a deploy user that can only run systemctl restart nginx via sudo, has a locked password (key-only login), and an SSH key already provided in /tmp/deploy.pub. Sequence: (1) useradd -m -s /bin/bash deploy; (2) passwd -l deploy — locks the password (login by key only); (3) install -d -m 700 -o deploy -g deploy /home/deploy/.ssh then install -m 600 -o deploy -g deploy /tmp/deploy.pub /home/deploy/.ssh/authorized_keys; (4) echo 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx' > /etc/sudoers.d/deploy then visudo -c to validate. Verify: sudo -l -U deploy lists the one allowed command; ssh -i key deploy@host sudo systemctl restart nginx works; any other sudo invocation is denied.
Key takeaways
  • usermod -aG group user — the -a (append) is mandatory; without it you replace the user's supplementary group list.
  • chage -M / -m / -W sets max / min / warn days; chage -d 0 user forces a password change on next login.
  • Edit /etc/sudoers only via visudo (syntax-checked); add overrides to /etc/sudoers.d/ drop-ins so package upgrades don't clobber them.
⚡ Mini-quiz — Drill useradd options, group append vs replace, password aging, and sudoers syntax.
Quick quiz →
04

Permissions & Access Control3 lessons

Standard rwx for owner/group/other; special bits (SUID, SGID, sticky) for shared services and team folders; ACLs for fine-grained per-user permission grants where standard owner/group/other isn't enough. Numeric and symbolic chmod must both be reflex.

chmod chown umask suid sgid sticky setfacl getfacl
~3h
📖 Read in-depth chapter
Standard rwx for owner/group/other; special bits — SUID, SGID, sticky — for shared binaries and team folders; setfacl / getfacl for fine-grained per-user grants. Numeric (0755) and symbolic (u+rwx,g+rx,o+rx) chmod syntaxes must both be reflex — the exam alternates between them and you can't afford the conversion math.
Lesson 4.1 Standard permissions — rwx, chmod, chown, umask

Numeric-to-symbolic conversion must be automatic by exam day. The single most common trap: r without x on a directory lets users list names but not access anything inside.

Key concepts
  • Permission triplets (rwx): Each file has three permission sets: owner (u), group (g), and others (o). r (read = 4), w (write = 2), x (execute = 1). On directories, r lists contents, w allows creating/deleting files, x allows entering the directory.
  • Chmod numeric & symbolic: chmod 755 file sets rwxr-xr-x. chmod u+x file adds execute for the owner. chmod go-w file removes write for group and others. chmod -R 750 /dir applies permissions recursively to a directory tree.
  • Chown & chgrp: chown user:group file changes both owner and group. chown -R user:group /dir applies recursively. chgrp groupname file changes only the group. Only root can change file ownership; group members can change group ownership.
  • Umask: The umask subtracts permissions from the default (666 for files, 777 for directories). A umask of 022 creates files with 644 and directories with 755. Set in /etc/bashrc or ~/.bashrc. Use umask to view and umask 027 to set.
  • Directory permissions: Execute (x) on a directory means you can cd into it and access files by name. Read (r) means you can list its contents with ls. Without x, even if you know a file's name, you cannot access it. This distinction is a common exam topic.
Concrete example

Task: project directory /srv/app readable by group app, with the binary /srv/app/run executable only by group app members. Solution: chown + chmodchown -R root:app /srv/app, chmod 750 /srv/app (rwxr-x---), chmod 750 /srv/app/run. Verify by becoming a non-group user: sudo -u nobody ls /srv/app should fail with permission denied. Read it back: ls -ld /srv/app.

Key takeaway: x on a directory = "may enter". 750 is the canonical group-only directory mode. Conversion: 750 = rwxr-x---.
⚡ Mini-quiz
Drill chmod + chown scenarios → study mode (10 questions).
Lesson 4.2 Special permissions — SUID, SGID, sticky bit

Special bits are the difference between "users can collaborate" and "users can wipe each other's work". The 4-digit chmod (e.g., 2770) is the exam-canonical way to express team folders.

Key concepts
  • SUID (Set User ID): When set on an executable file (chmod u+s file or 4xxx), the process runs with the file owner's privileges. Example: /usr/bin/passwd has SUID so regular users can modify /etc/shadow. SUID has no effect on directories.
  • SGID (Set Group ID): On files (chmod g+s file or 2xxx), the process runs with the file's group privileges. On directories, new files and subdirectories inherit the directory's group ownership instead of the creator's primary group — essential for shared collaborative directories.
  • Sticky bit: When set on a directory (chmod +t /dir or 1xxx), only the file owner, directory owner, or root can delete or rename files within it. The classic example is /tmp (permissions drwxrwxrwt), which prevents users from deleting each other's files.
  • Identifying special permissions: In ls -l output, SUID appears as s in the owner execute position, SGID as s in the group execute position, and sticky bit as t in the others execute position. Uppercase S or T means the underlying execute bit is not set.
  • Security implications: SUID executables are a security risk — find them with find / -perm -4000. SGID on binaries is less common. Never set SUID on shell scripts (the kernel ignores it for security). Regularly audit SUID/SGID files on production systems.
Concrete example

Task: shared drop-box at /data/share where all users can write but only the file owner can delete their own file. Solution: sticky + SGID combochmod 3770 /data/share (digits: 3 = SGID + sticky, 770 = rwxrwx---). New files inherit the group; the sticky bit blocks cross-user deletion. Verify ls -ld /data/share shows drwxrws--T. Audit SUID system-wide periodically: find / -perm -4000 -type f 2>/dev/null.

Key takeaway: 4 = SUID, 2 = SGID, 1 = sticky — combine in the leading digit. SGID + sticky is the team-folder + drop-box recipe.
⚡ Mini-quiz
Drill special-bit scenarios → quick quiz (5 questions).
Lesson 4.3 Access Control Lists (ACLs) — fine-grained grants

When the requirement says "alice gets rw, bob gets r, on the same file" — standard permissions can't express it. ACLs are the answer. Default ACLs on directories make new files inherit the permission grant automatically.

Key concepts
  • Why ACLs: Standard Linux permissions only support one owner and one group. ACLs extend this to grant specific permissions to multiple users and groups. A + at the end of ls -l output indicates an ACL is present.
  • Setting ACLs: setfacl -m u:username:rwx /file grants a specific user rwx access. setfacl -m g:groupname:rx /file grants a specific group rx access. setfacl -x u:username /file removes a specific ACL entry. setfacl -b /file removes all ACLs.
  • Default ACLs: setfacl -m d:u:username:rwx /dir sets a default ACL on a directory so that new files and subdirectories automatically inherit the specified permissions. Default ACLs only apply to directories, not files.
  • ACL mask: The mask defines the maximum effective permissions for named users and groups (not the owner). setfacl -m m::rx /file sets the mask to rx, limiting all named users/groups to at most rx regardless of their individual ACL entries. The mask is recalculated automatically when ACLs change.
  • Viewing ACLs: getfacl /file displays all ACL entries including owner, group, mask, and other permissions. The output shows effective permissions when the mask restricts an entry. Use getfacl -R /dir to recursively view ACLs on a directory tree.
Concrete example

Task: /srv/reports must let alice read+write, bob read-only, and new files must inherit those grants. Solution: setfacl + default ACLssetfacl -m u:alice:rwx /srv/reports, setfacl -m u:bob:rx /srv/reports, then default counterparts: setfacl -m d:u:alice:rwx /srv/reports, setfacl -m d:u:bob:rx /srv/reports. Test by creating a file as root inside and check getfacl /srv/reports/newfile — alice and bob entries appear automatically.

Key takeaway: ACLs for per-user grants beyond owner/group/other. d: prefix on a directory makes new entries inherit. Always verify with getfacl.
⚡ Mini-quiz
Drill ACL scenarios → study mode (10 questions).
💻 Worked scenario — Fix a shared dev directory so new files inherit the group
Task: members of group devs drop files into /srv/team, but new files keep being owned by each user's primary group. Sequence: (1) chgrp devs /srv/team; (2) chmod 2775 /srv/team — the leading 2 sets SGID, forcing new entries to inherit the directory's group; (3) setfacl -d -m g:devs:rwx /srv/team — default ACL guarantees rwx for the group regardless of the user's umask. Verify: su - alice; touch /srv/team/x; ls -la /srv/team/x — group should be devs; getfacl /srv/team shows the default mask carrying through.
Key takeaways
  • Special bits: SUID (4xxx) runs as owner, SGID (2xxx) on a dir forces new files to inherit the group, sticky (1xxx) on a shared dir means only owners can delete their files (/tmp pattern).
  • Apply a default ACL on a collaborative directory with setfacl -d -m g:devops:rwx /srv/shared so new files inherit the team grant automatically.
  • umask subtracts from 666 (files) or 777 (dirs); set system-wide in /etc/profile or per-user in ~/.bashrc.
⚡ Mini-quiz — Drill numeric ↔ symbolic chmod, SUID/SGID/sticky scenarios, and ACL inheritance.
Quick quiz →
05

Networking3 lessons

NetworkManager via nmcli is the only persistent-config path you should use on the exam. Hostname via hostnamectl. Connection testing with ping, ss, dig. SSH server hardening with key-based auth and root-login disabled. Every "network" task ends with "make it survive a reboot".

nmcli nmtui hostnamectl ip-addr ss sshd ssh-keygen authorized_keys
~3h
📖 Read in-depth chapter
nmcli is the only persistent network-config path you should use on the exam — anything you type into ip addr add dies at the next reboot. Hostname via hostnamectl, troubleshooting with ping / ss / dig, SSH server hardening with key-based auth and disabled root login. Every "network" task ends with the same silent acceptance criterion: it must survive a reboot.
Lesson 5.1 Network configuration — nmcli, hostnamectl, DNS

Use nmcli for every persistent network change. Never edit ifcfg files by hand — Network­Manager owns them. Set the hostname early; it's a recurring sub-task on every exam.

Key concepts
  • NetworkManager & nmcli: NetworkManager is the default network management daemon on RHEL. nmcli con show lists connections. nmcli con mod "eth0" ipv4.addresses 192.168.1.10/24 ipv4.gateway 192.168.1.1 ipv4.dns 8.8.8.8 ipv4.method manual configures a static IP. nmcli con up "eth0" activates the connection.
  • nmtui: A text-based user interface for NetworkManager. Run nmtui to edit connections, activate/deactivate connections, and set the system hostname. Useful when you need a quick visual interface during the exam.
  • IP address verification: ip addr show (or ip a) displays all interfaces and their IP addresses. ip route show displays the routing table and default gateway. ip link show displays link-layer information and interface state (UP/DOWN).
  • Hostname configuration: hostnamectl set-hostname server1.example.com sets the static hostname persistently. The hostname is stored in /etc/hostname. Add local name resolution entries in /etc/hosts for hosts that do not have DNS records.
  • DNS configuration: DNS servers are configured through NetworkManager (preferred) or directly in /etc/resolv.conf. The /etc/nsswitch.conf file controls name resolution order (files before dns means /etc/hosts is checked first).
Concrete example

Task: set hostname to server1.example.com and configure static IP 192.168.10.20/24 with gateway 192.168.10.1 and DNS 8.8.8.8 on ens3. Solution: hostnamectl + nmclihostnamectl set-hostname server1.example.com, then nmcli con mod ens3 ipv4.addresses 192.168.10.20/24 ipv4.gateway 192.168.10.1 ipv4.dns 8.8.8.8 ipv4.method manual, finally nmcli con up ens3 to apply. Verify with ip a, ip route, and cat /etc/resolv.conf.

Key takeaway: always nmcli, never raw ifcfg edits. nmcli con up reactivates after a mod. Hostname via hostnamectl, period.
⚡ Mini-quiz
Drill nmcli + hostname scenarios → study mode (10 questions).
Lesson 5.2 Network troubleshooting — ping, ss, dig

When a service "doesn't work", a layered diagnostic walk — link → IP → gateway → DNS → firewall → listener — finds the cause faster than guesswork. The exam clocks every minute, so a systematic check pays.

Key concepts
  • Connection testing: ping -c 4 host tests ICMP connectivity. traceroute host (or tracepath) shows the path packets take to a destination, identifying where connectivity fails. curl -v http://host tests HTTP connectivity at the application layer.
  • Socket statistics: ss -tlnp shows listening TCP sockets with process information. ss -ulnp shows listening UDP sockets. ss -an shows all connections with numeric addresses. This replaces the deprecated netstat command on RHEL.
  • DNS troubleshooting: dig example.com queries DNS and shows detailed response information. nslookup example.com provides simpler DNS lookup output. host example.com performs a quick DNS resolution check. Verify /etc/resolv.conf for correct nameserver entries.
  • Firewall basics: firewall-cmd --list-all shows the current zone configuration including allowed services and ports. If connectivity fails, check whether the required service or port is allowed through the firewall before investigating other causes.
  • Network teaming & bridging: Network teams bond multiple NICs for redundancy or load balancing using nmcli con add type team. Network bridges connect virtual machines to the physical network. Both are configured through NetworkManager and tested at an awareness level on the RHCSA.
Concrete example

Symptom: web service unreachable from client. Diagnostic walk: 1. ip link show ens3 — interface UP? 2. ip a — IP assigned? 3. ping -c 2 <gateway> — L3 to gateway? 4. dig example.com — DNS resolves? 5. firewall-cmd --list-all — http service allowed? 6. ss -tlnp | grep :80 — httpd actually listening? Each step rules out one layer; the first failure tells you where to dig.

Key takeaway: layered walk: link → IP → gateway → DNS → firewall → listener. ss replaces netstat; learn it cold.
⚡ Mini-quiz
Drill network-troubleshooting scenarios → quick quiz (5 questions).
Lesson 5.3 SSH server — sshd config, keys, hardening

SSH is the only way you reach the second exam VM. The exam often asks you to disable root login, enable key-based authentication, or change the listening port — all of which involve sshd_config, a service restart, and a firewall + SELinux port update.

Key concepts
  • sshd_config essentials: /etc/ssh/sshd_config controls the server. Key directives: PermitRootLogin no, PasswordAuthentication no, PubkeyAuthentication yes, Port 22. After any edit, systemctl restart sshd to apply. Validate syntax first with sshd -t.
  • Key-based authentication: Client generates a key pair with ssh-keygen -t ed25519. Public key goes to the server in ~/.ssh/authorized_keys (mode 600), under a directory with mode 700 owned by the target user. ssh-copy-id user@host automates the deployment.
  • Drop-in config files: /etc/ssh/sshd_config.d/*.conf are parsed before the main file. RHEL ships with /etc/ssh/sshd_config.d/50-redhat.conf; put your overrides in a higher-numbered file like 99-custom.conf so they win.
  • Non-standard port + SELinux: Moving sshd to a non-default port requires semanage port -a -t ssh_port_t -p tcp 2222 AND firewall-cmd --add-port=2222/tcp --permanent + reload. Skipping either makes the service look broken when SELinux or the firewall is the cause.
  • Connection client options: ssh -i ~/.ssh/id_ed25519 user@host picks a specific key. ~/.ssh/config per-host aliases (Host prod, HostName ..., User ..., IdentityFile ...) save typing. Always test sshd -t after edits to avoid locking yourself out.
Concrete example

Task: disable root SSH login and password auth, allow only key-based auth for user admin. Solution: drop-in override + key deploy — create /etc/ssh/sshd_config.d/99-hardening.conf with PermitRootLogin no and PasswordAuthentication no, then sshd -t to validate, systemctl restart sshd. As admin: ssh-keygen -t ed25519, copy the pub key to the server's ~admin/.ssh/authorized_keys with mode 600 inside a mode-700 .ssh directory owned by admin. Test with ssh admin@host — succeeds with key, root and password attempts fail.

Key takeaway: always sshd -t before restart, or you can lock yourself out. Non-standard port = semanage port -a -t ssh_port_t + firewalld update.
⚡ Mini-quiz
Drill SSH hardening scenarios → study mode (10 questions).
💻 Worked scenario — Persistent static IP via nmcli
Task: assign 192.168.10.50/24 with gateway 192.168.10.1 and DNS 1.1.1.1 to interface ens3, surviving reboot. Sequence: (1) nmcli con show — find the active profile name (often System ens3 or Wired connection 1); (2) nmcli con mod 'System ens3' ipv4.addresses 192.168.10.50/24 ipv4.gateway 192.168.10.1 ipv4.dns 1.1.1.1 ipv4.method manual; (3) nmcli con down 'System ens3' && nmcli con up 'System ens3'. Verify: ip -br addr show ens3 shows the new address; ip route shows the default via the gateway; ping -c2 1.1.1.1 succeeds; cat /etc/resolv.conf lists the DNS.
Key takeaways
  • nmcli con mod <name> ipv4.addresses ... ipv4.method manual + nmcli con up <name> — anything not done via NetworkManager won't persist.
  • ssh-keygenssh-copy-id user@host deploys the public key; then disable PasswordAuthentication + PermitRootLogin in /etc/ssh/sshd_config and systemctl restart sshd.
  • Use ss -tlnp (modern replacement for netstat) to see who's listening on which port; dig +short gives a one-line resolve check.
⚡ Mini-quiz — Drill nmcli persistence, SSH key-auth setup, and connectivity-troubleshooting commands.
Quick quiz →
06

Firewall & SELinux3 lessons

firewalld zones + permanent rules, then the SELinux trifecta: file contexts via semanage fcontext + restorecon, booleans via setsebool -P, port labels via semanage port. SELinux must stay enforcing — the exam zero-grades attempts to disable it.

firewalld firewall-cmd zones selinux enforcing semanage restorecon setsebool
~4h
📖 Read in-depth chapter
firewalld zones with permanent rules, then the SELinux trifecta: file contexts via semanage fcontext + restorecon, booleans via setsebool -P, port labels via semanage port. SELinux must stay enforcing — the exam zero-grades any attempt to setenforce 0 or set SELINUX=disabled. Diagnose with ausearch / sealert instead.
Lesson 6.1 firewalld — zones, services, ports, reload

firewalld is on every exam. The #1 mistake: forgetting --permanent and losing rules at reboot. Rules without --permanent are runtime-only and zero-graded.

Key concepts
  • Zones & default zone: firewalld organizes rules into zones (public, work, home, dmz, trusted, etc.). Each network interface is assigned to a zone. The default zone (usually public) applies to interfaces not explicitly assigned. firewall-cmd --get-default-zone shows the current default.
  • Adding services & ports: firewall-cmd --add-service=http --permanent allows HTTP traffic. firewall-cmd --add-port=8080/tcp --permanent opens a custom port. The --permanent flag writes the rule to disk; without it, rules are runtime-only and lost on reload.
  • Runtime vs permanent: Runtime rules take effect immediately but are lost on firewall-cmd --reload or reboot. Permanent rules require --reload to take effect. Best practice: add rules with --permanent, then run firewall-cmd --reload to apply them.
  • Rich rules: firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept' --permanent creates granular rules based on source address, destination, service, port, and action (accept/reject/drop).
  • Verification: firewall-cmd --list-all displays all rules in the active zone. firewall-cmd --list-services shows allowed services. firewall-cmd --list-ports shows allowed ports. firewall-cmd --get-active-zones shows zone-to-interface assignments.
Concrete example

Task: permanently allow http, https, and custom port 8080/tcp; restrict SSH access to only the 192.168.10.0/24 subnet. Solution: permanent service + rich rulefirewall-cmd --add-service=http --add-service=https --add-port=8080/tcp --permanent, then firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" service name="ssh" accept' --permanent, then remove the default ssh service with firewall-cmd --remove-service=ssh --permanent, finally firewall-cmd --reload. Verify firewall-cmd --list-all.

Key takeaway: always --permanent + --reload. Verify with --list-all. Predefined service names (http, https, ssh, nfs) save typing port numbers.
⚡ Mini-quiz
Drill firewalld scenarios → study mode (10 questions).
Lesson 6.2 SELinux fundamentals — modes, contexts, type enforcement

SELinux is mandatory access control on top of regular permissions. Every file, process, and port has a type label. Tasks where "permissions look right but service still fails" are almost always SELinux.

Key concepts
  • SELinux modes: Enforcing (actively blocks policy violations and logs them), Permissive (logs violations but does not block — useful for troubleshooting), Disabled (SELinux is completely off). Check the current mode with getenforce. Toggle between enforcing and permissive with setenforce 1 or setenforce 0.
  • Persistent SELinux configuration: The file /etc/selinux/config controls the mode at boot. Set SELINUX=enforcing for production systems. Changing from disabled to enforcing requires a reboot and filesystem relabel, which can be time-consuming.
  • SELinux contexts: Every file, process, and port has a security context in the format user:role:type:level. The type field is the most important for policy enforcement. View file contexts with ls -Z, process contexts with ps -eZ, and port contexts with semanage port -l.
  • Common context types: httpd_sys_content_t for web content served by Apache, sshd_t for the SSH daemon process, user_home_t for user home directories. When a process type does not have permission to access a file type, SELinux denies the access.
  • Type enforcement: SELinux policy rules define which process types (domains) can access which file types. For example, httpd_t can read httpd_sys_content_t but not user_home_t. This Mandatory Access Control (MAC) operates on top of standard Linux DAC permissions.
Concrete example

Symptom: httpd returns 403 on a file you can cat as root. Diagnostic: compare process and file contextsps -eZ | grep httpd shows process type httpd_t; ls -Z /var/www/html/index.html shows file type. If file type is user_home_t instead of httpd_sys_content_t (e.g., file copied from a user home), httpd is denied. Confirm in /var/log/audit/audit.log with ausearch -m avc -ts recent.

Key takeaway: SELinux stays enforcing. Mismatched type between process and file = denial. Compare with ls -Z + ps -eZ.
⚡ Mini-quiz
Drill SELinux fundamentals → quick quiz (5 questions).
Lesson 6.3 SELinux troubleshooting — restorecon, booleans, ports

Three remediation tools cover ~95% of SELinux denials: restorecon for wrong file contexts, setsebool -P for blocked behaviors, semanage port for non-default service ports.

Key concepts
  • Restoring contexts: restorecon -Rv /path resets file contexts to the policy defaults. This is the most common fix when files have been copied or moved and their contexts are wrong. Moving a file preserves its original context; copying inherits the destination context.
  • Managing file contexts: semanage fcontext -a -t httpd_sys_content_t "/custom/web(/.*)?" adds a permanent context rule for a custom directory. Then run restorecon -Rv /custom/web to apply. This is required when serving web content from non-default directories.
  • SELinux booleans: Booleans are on/off switches for specific policy behaviors. getsebool -a | grep httpd lists all HTTP-related booleans. setsebool -P httpd_enable_homedirs on persistently enables a boolean (-P for permanent). Without -P, the change is lost on reboot.
  • Audit log analysis: SELinux denials are logged in /var/log/audit/audit.log. ausearch -m avc -ts recent finds recent denials. Install setroubleshoot-server for sealert, which provides human-readable explanations and suggested fixes for each denial.
  • Port contexts: semanage port -l | grep http lists ports assigned to HTTP types. semanage port -a -t http_port_t -p tcp 8888 allows httpd to listen on a custom port. Without this, SELinux blocks the service from binding to non-standard ports.
Concrete example

Task: serve web content from /srv/www (non-default) and let httpd listen on port 8888. Solution: semanage + restorecon + boolean + firewallsemanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?", restorecon -Rv /srv/www; semanage port -a -t http_port_t -p tcp 8888; setsebool -P httpd_can_network_connect on if it connects out; firewall-cmd --add-port=8888/tcp --permanent && firewall-cmd --reload. Verify with ls -Z /srv/www and semanage port -l | grep http_port_t.

Key takeaway: three tools — restorecon, setsebool -P, semanage port -a — cover almost every SELinux fix. Always -P for permanent.
⚡ Mini-quiz
Drill SELinux remediation scenarios → study mode (10 questions).
💻 Worked scenario — Open a non-standard Apache port through firewalld + SELinux
Task: Apache must listen on 8443/tcp. By default both firewalld and SELinux block the new port. Sequence: (1) firewall-cmd --permanent --add-port=8443/tcp && firewall-cmd --reload; (2) semanage port -a -t http_port_t -p tcp 8443 (if missing: dnf -y install policycoreutils-python-utils); (3) edit /etc/httpd/conf/httpd.conf to add Listen 8443; (4) systemctl restart httpd. Verify: ss -lntp | grep 8443 shows httpd listening; firewall-cmd --list-ports includes 8443/tcp; semanage port -l | grep http_port_t includes 8443. If a request 503s, ausearch -m avc -ts recent reveals the missing label.
Key takeaways
  • Every firewall-cmd change needs --permanent + --reload to survive reboot; without --permanent the rule disappears on next restart.
  • Move a service's docroot? Always relabel: semanage fcontext -a -t httpd_sys_content_t "/new(/.*)?" then restorecon -Rv /new. Skipping this is the #1 SELinux denial cause.
  • Toggle a boolean permanently with setsebool -P httpd_can_network_connect on; relabel a non-standard port with semanage port -a -t http_port_t -p tcp 8080.
⚡ Mini-quiz — Drill firewalld zones, file-context relabeling, boolean toggles, and SELinux troubleshooting.
Quick quiz →
07

Process Management3 lessons

Process inspection with ps, top, pgrep; signals and nice priority; systemctl service control and journalctl log inspection; writing custom systemd unit files for new services. Every service the exam asks you to configure must be both started AND enabled — systemctl enable --now does both in one shot.

ps-top kill-signals nice-renice systemctl enable-now unit-files journalctl targets
~3h
📖 Read in-depth chapter
Process inspection with ps / top / pgrep, signals + nice priority, and the systemd surface: systemctl for control, journalctl for logs, custom unit files for new services. Every service the exam configures must be both started and enabledsystemctl enable --now <svc> does both in one shot and is the correct reflex answer.
Lesson 7.1 Process control — ps, kill, nice, jobs

Find the runaway process, change its priority, kill it if necessary. The exam treats this as practical: identify the resource hog with top, then adjust or terminate.

Key concepts
  • Viewing processes: ps aux lists all processes with user, PID, CPU/memory usage, and command. ps -ef provides a similar view with parent PID. top (or htop if installed) provides a real-time, interactive view of system resource usage sorted by CPU or memory.
  • Killing processes: kill PID sends SIGTERM (signal 15), which requests graceful termination. kill -9 PID sends SIGKILL, which forces immediate termination (cannot be caught or ignored). killall processname kills all processes with a given name. pkill -u username kills all processes owned by a user.
  • Process priority (nice): Nice values range from -20 (highest priority) to 19 (lowest priority). Default is 0. nice -n 10 command starts a process with lower priority. renice -n 5 -p PID changes the priority of a running process. Only root can set negative nice values.
  • Job control: Ctrl+Z suspends the foreground process. bg %1 resumes it in the background. fg %1 brings it to the foreground. jobs lists all background jobs. nohup command & runs a command immune to hangup signals, so it continues after logout.
  • Signal types: SIGTERM (15) — polite termination request. SIGKILL (9) — forced termination, cannot be caught. SIGHUP (1) — often used to reload configuration. SIGSTOP (19) — pauses a process. SIGCONT (18) — resumes a paused process. List all signals with kill -l.
Concrete example

Task: a runaway compute.py is hogging CPU. Identify and de-prioritise it without killing. Solution: top + renice — open top, press P to sort by %CPU, note the PID. Exit; renice -n 15 -p <PID> drops it near the lowest priority. If it must die, kill <PID> (SIGTERM) first; only escalate to kill -9 <PID> if it ignores SIGTERM. Verify with ps -o pid,ni,comm -p <PID>.

Key takeaway: always SIGTERM before SIGKILL. renice for live priority. nohup & for "survive my logout".
⚡ Mini-quiz
Drill process-control scenarios → study mode (10 questions).
Lesson 7.2 systemd services — start, enable, mask, targets

systemctl is the most-used tool on the exam. Every service the exam configures must be both started AND enabled — graders check that it survives a reboot.

Key concepts
  • Service management: systemctl start httpd starts a service. systemctl stop httpd stops it. systemctl restart httpd restarts (stop + start). systemctl reload httpd reloads configuration without stopping. systemctl status httpd shows the current state, PID, and recent log entries.
  • Enabling & disabling: systemctl enable httpd creates symlinks so the service starts at boot. systemctl disable httpd removes those symlinks. systemctl enable --now httpd enables and starts in one command. systemctl is-enabled httpd checks the boot-time status.
  • Masking services: systemctl mask httpd prevents a service from being started by any means (manual or dependency). systemctl unmask httpd reverses the mask. Masking creates a symlink to /dev/null. This is stronger than disabling, which only prevents automatic startup at boot.
  • systemd targets: Targets replace SysVinit runlevels. multi-user.target is equivalent to runlevel 3 (text mode). graphical.target is equivalent to runlevel 5 (GUI). systemctl get-default shows the default target. systemctl set-default multi-user.target changes it.
  • Journalctl: journalctl -u httpd shows logs for a specific unit. journalctl -f follows the log in real time. journalctl --since "1 hour ago" filters by time. journalctl -p err shows only error-level and above messages. The journal is stored in /run/log/journal by default (volatile).
Concrete example

Task: install httpd, make sure it starts at boot, verify it's actually running, troubleshoot if it fails. Solution: install + enable --now + verifydnf -y install httpd, systemctl enable --now httpd. Verify both states: systemctl is-active httpd (running?) and systemctl is-enabled httpd (boot?). If it fails, systemctl status httpd (last error) and journalctl -xeu httpd (full context). Check listener: ss -tlnp | grep :80.

Key takeaway: enable --now is the canonical RHCSA combo. Verify with is-active + is-enabled. journalctl -xeu <unit> is your first stop for any service failure.
⚡ Mini-quiz
Drill systemd service scenarios → quick quiz (5 questions).
Lesson 7.3 Writing systemd unit files

Beyond starting prebuilt services, RHCSA may ask you to make a custom script a real systemd-managed service — auto-restart on failure, logged to the journal, enabled at boot.

Key concepts
  • Unit file location: System-wide unit files live in /etc/systemd/system/<name>.service (admin-owned, highest precedence). Distro-supplied units live in /usr/lib/systemd/system/ — never edit those directly; copy or override.
  • The three sections: [Unit] (description, dependencies via After= / Requires=), [Service] (Type=simple/forking/oneshot, ExecStart=, Restart=on-failure, User=), [Install] (WantedBy=multi-user.target).
  • Reload after editing: Any change to a unit file requires systemctl daemon-reload before the next start or restart. Forgetting this is the #1 reason a "fixed" unit still behaves the old way.
  • Overrides via drop-ins: systemctl edit httpd opens an empty override at /etc/systemd/system/httpd.service.d/override.conf. Add only the directives you want to change — safer than copying the whole vendor unit. Always include the section header (e.g., [Service]).
  • Verifying the unit: systemctl cat <unit> shows the effective merged unit (vendor + overrides). systemctl show <unit> dumps every resolved property. After daemon-reload, systemctl status reflects the new definition.
Concrete example

Task: turn /usr/local/bin/sync.sh into a service that auto-restarts on failure. Solution: write unit + daemon-reload + enable --now — create /etc/systemd/system/sync.service with [Unit] Description=Custom sync\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/sync.sh\nRestart=on-failure\nUser=root\n\n[Install]\nWantedBy=multi-user.target. Then systemctl daemon-reload, systemctl enable --now sync. Verify with systemctl status sync and journalctl -u sync.

Key takeaway: three sections — Unit, Service, Install. Always daemon-reload after edits. systemctl edit for overriding vendor units cleanly.
⚡ Mini-quiz
Drill systemd unit-file scenarios → study mode (10 questions).
💻 Worked scenario — Tame a runaway CPU-hogging process
Task: load average is 15 on a 2-core VM. Identify the culprit and stop the bleeding without a reboot. Sequence: (1) top -b -n1 | head -20 — top of CPU% column; note the PID; (2) ps -p PID -o pid,user,etime,cmd for context (how long it has been running); (3) strace -p PID -e trace=openat,read 2>&1 | head -50 — what it's looping on; (4) deprioritise rather than kill if the work is legitimate: renice +15 -p PID, or stop it cleanly with kill -TERM PID; (5) for a recurring offender, cap it via systemd drop-in: systemctl edit unit-name and add [Service]\nCPUQuota=50%. Verify: uptime load decays; systemctl show unit-name -p CPUQuotaPerSecUSec reflects the cap.
Key takeaways
  • systemctl enable --now <svc> = enable at boot + start immediately. Forgetting the --now (or just running start) is the most graded-against mistake.
  • Custom units live in /etc/systemd/system/<name>.service; after any edit run systemctl daemon-reload before starting the service.
  • Persist journal logs across reboots: create /var/log/journal (or set Storage=persistent in /etc/systemd/journald.conf) and restart systemd-journald.
⚡ Mini-quiz — Drill systemctl lifecycle, unit-file syntax, signals, and journalctl filtering.
Quick quiz →
08

Package Management3 lessons

dnf for installs, updates, group packages, and module streams. rpm for low-level queries — "which package owns this file?" Configure custom repositories in /etc/yum.repos.d/ for the exam-supplied local mirror. Know which command to use when.

dnf yum rpm groups module-streams repo-config gpgcheck dnf-history
~3h
📖 Read in-depth chapter
dnf for installs, updates, group packages, and module streams; rpm for low-level queries — "which package owns this file?". Custom repositories live in /etc/yum.repos.d/*.repo for the exam-supplied local mirror. Know which tool to reach for: dnf resolves dependencies, rpm queries the local database.
Lesson 8.1 DNF — install, update, groups, module streams

DNF (the YUM successor on RHEL 8/9) is the daily driver for everything: install, update, groups, modules. Same syntax as YUM — the alias is preserved for muscle memory.

Key concepts
  • Core DNF operations: dnf install httpd installs a package and its dependencies. dnf remove httpd uninstalls it. dnf update updates all packages. dnf update httpd updates a specific package. DNF is the replacement for YUM on RHEL 8+ and they share the same syntax.
  • Searching & querying: dnf search keyword finds packages by name or description. dnf info httpd shows package details (version, size, description). dnf list installed shows all installed packages. dnf provides /path/to/file identifies which package owns a specific file.
  • Group packages: dnf group list lists available package groups. dnf group install "Development Tools" installs a full group of related packages. Groups bundle common tools (compilers, libraries, server components) for convenient installation.
  • Repository management: Repos are configured in /etc/yum.repos.d/*.repo files. Key fields: baseurl or mirrorlist, enabled=1, gpgcheck=1, gpgkey. dnf repolist shows enabled repos. dnf config-manager --add-repo URL adds a new repository.
  • Module streams: dnf module list shows available module streams (e.g., python36, python38, python39). dnf module enable python39 selects a stream. dnf module install python39 installs the module profile. Streams allow multiple versions of software to coexist in the same repository.
Concrete example

Task: install the "Development Tools" group, then install python 3.9 specifically. Solution: group install + module switchdnf -y group install "Development Tools", then dnf module list python39 to confirm availability, dnf module enable -y python39, dnf module install -y python39. Verify with python3.9 --version. If a different python stream was previously active, run dnf module reset python first.

Key takeaway: dnf is your default. Modules need enable + install; reset to clear a previous stream choice.
⚡ Mini-quiz
Drill DNF scenarios → study mode (10 questions).
Lesson 8.2 RPM — low-level queries and verification

RPM is the layer underneath DNF. Use it for queries — DNF for installs. rpm -qf and rpm -ql answer most "where did this file come from?" questions in seconds.

Key concepts
  • RPM queries: rpm -qa lists all installed packages. rpm -qi httpd shows detailed information about an installed package. rpm -ql httpd lists all files installed by a package. rpm -qf /usr/sbin/httpd identifies which package owns a file. rpm -qd httpd lists documentation files.
  • RPM installation: rpm -ivh package.rpm installs a local RPM file (i = install, v = verbose, h = hash progress). rpm -Uvh package.rpm upgrades (installs if not present). rpm -e packagename removes a package. RPM does not resolve dependencies — use DNF for that.
  • GPG key verification: rpm --import https://url/RPM-GPG-KEY imports a GPG public key. rpm -K package.rpm verifies the signature and integrity of an RPM file. GPG keys ensure packages have not been tampered with and come from a trusted source.
  • RPM vs DNF: RPM operates on individual package files without dependency resolution. DNF wraps RPM, adding repository support, automatic dependency resolution, and group/module management. Use RPM for querying installed packages and DNF for installing, updating, and removing.
  • Verifying installed files: rpm -V httpd compares installed files against package metadata; output flags S (size), M (mode), 5 (md5 hash), T (timestamp) for any drift. Useful for spotting tampered binaries or config files.
Concrete example

Task: figure out which package owns /usr/sbin/sshd, list its config files, and check whether any have been modified. Solution: rpm -qf + rpm -qc + rpm -Vrpm -qf /usr/sbin/sshd returns openssh-server-.... rpm -qc openssh-server lists every config file shipped. rpm -V openssh-server flags any with 5 (md5 differs from original) — those have been edited.

Key takeaway: rpm -qf $(which cmd) for "what package owns this?" — fastest answer. rpm -V for drift detection.
⚡ Mini-quiz
Drill RPM query scenarios → quick quiz (5 questions).
Lesson 8.3 Configuring a custom repository

RHCSA frequently hands you a URL for a local mirror and expects you to wire it up as a yum/dnf repo. The pattern is identical every time: drop a .repo file in /etc/yum.repos.d/ with the right four fields.

Key concepts
  • Anatomy of a .repo file: Each repo block needs an [id] header, name= (free-text label), baseurl= (or mirrorlist=), enabled=1, and either gpgcheck=0 (skip verification) or gpgcheck=1 + gpgkey=.
  • Location & precedence: Files go in /etc/yum.repos.d/, must end in .repo, and are read in alphabetical order. Multiple [id] blocks per file are allowed but one repo per file is cleaner for the exam.
  • GPG handling: If the exam provides a GPG key URL, set gpgcheck=1 and gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-... or a remote URL. rpm --import <key-url> trusts the key system-wide. If no key is provided, gpgcheck=0 is acceptable for a local lab mirror.
  • Verifying the repo: dnf repolist must show the new repo enabled. dnf repolist all includes disabled ones. dnf --disablerepo='*' --enablerepo=myrepo list available | head isolates the new repo to confirm packages are reachable.
  • dnf history: dnf history lists every transaction. dnf history undo <id> reverses one. Useful when an exam task asks you to roll back a misinstall.
Concrete example

Task: configure a local mirror at http://mirror.example.com/rhel9/baseos/ as repo localbase with no GPG check. Solution: drop a .repo file — create /etc/yum.repos.d/localbase.repo with [localbase]\nname=Local BaseOS Mirror\nbaseurl=http://mirror.example.com/rhel9/baseos/\nenabled=1\ngpgcheck=0. Verify with dnf repolist (must show localbase) and try a test install: dnf -y install bind-utils. If it pulls from localbase, you're done.

Key takeaway: four fields — [id], name, baseurl, enabled=1 — plus gpgcheck. Verify with dnf repolist before trusting it.
⚡ Mini-quiz
Drill repo configuration scenarios → study mode (10 questions).
💻 Worked scenario — Roll back a bad DNF transaction
Task: a dnf update ran 30 minutes ago and broke a production service. Revert just that transaction. Sequence: (1) dnf history list — find the offending transaction ID (e.g. 42); (2) dnf history info 42 confirms the packages that were upgraded; (3) dnf history undo 42 — restores the previous versions in one transaction; (4) systemctl restart broken-service and validate; (5) freeze the known-good versions: dnf install python3-dnf-plugin-versionlock then dnf versionlock add package-name. Verify: rpm -q package-name shows the rolled-back NVR; dnf versionlock list confirms the lock; dnf history shows the undo transaction.
Key takeaways
  • Add a local repo by dropping a *.repo file in /etc/yum.repos.d/ with baseurl=file:// or http:// + gpgcheck=0 if no key is provided.
  • Module streams: dnf module listdnf module enable nodejs:18dnf module install nodejs. Streams pin a version family for the lifetime of the system.
  • rpm -qf /path/to/file tells you which package owns a file; rpm -ql <pkg> lists everything an installed package shipped.
⚡ Mini-quiz — Drill dnf install / group / module streams, rpm queries, and custom repo configuration.
Quick quiz →
09

Scheduling & Logging3 lessons

Cron, anacron, and at for traditional scheduling. rsyslog + journald for logs, with persistent journal storage as a recurring exam objective. Modern systemd timers as the cron replacement — calendar expressions, paired service units, accuracy controls.

crontab anacron at rsyslog journalctl journal-persistent systemd-timers logrotate
~3h
📖 Read in-depth chapter
cron, anacron, and at for traditional scheduling. rsyslog + journald for logs, with persistent journal storage as a recurring exam objective. Modern systemd timers replace cron when you need calendar expressions, accuracy windows, or unit-aware scheduling.
Lesson 9.1 cron and at — recurring and one-shot jobs

Cron syntax must be reflex. The exam doesn't give partial credit for "almost right" — minute, hour, day, month, weekday, in that order, full stop.

Key concepts
  • Crontab: crontab -e edits the current user's cron schedule. crontab -l lists it. crontab -e -u username edits another user's crontab (as root). The format is: minute hour day-of-month month day-of-week command. Example: 30 2 * * 1 /scripts/backup.sh runs at 2:30 AM every Monday.
  • System cron: /etc/crontab is the system-wide crontab with an additional user field. Drop-in files in /etc/cron.d/ follow the same format. Directories /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, and /etc/cron.monthly/ run scripts placed within them at the named interval.
  • Anacron: Anacron runs missed cron jobs that were scheduled while the system was off. Configured in /etc/anacrontab. Unlike cron, anacron does not assume the system is running 24/7, making it suitable for desktops and laptops.
  • at command: at now + 5 minutes schedules a one-time job. Type commands, then press Ctrl+D to save. atq lists pending jobs. atrm jobnumber removes a scheduled job. Access control: /etc/at.allow and /etc/at.deny control which users can use at.
  • Absolute paths in cron: Cron runs with a minimal PATH (usually /usr/bin:/bin). Always use absolute paths in cron commands (e.g., /usr/bin/tar instead of tar). Redirect output: ... >> /var/log/job.log 2>&1.
Concrete example

Task: run /root/backup.sh at 02:30 every Monday and Friday as root. Solution: crontab -e — add the line 30 2 * * 1,5 /root/backup.sh >> /var/log/backup.log 2>&1. Verify with crontab -l. For a one-shot test in 5 minutes: echo "/root/backup.sh" | at now + 5 minutes, then atq to confirm queued.

Key takeaway: minute-hour-day-month-weekday, absolute paths only, redirect both streams. Use comma lists like 1,5 for specific days.
⚡ Mini-quiz
Drill cron syntax scenarios → study mode (10 questions).
Lesson 9.2 System logging — rsyslog, journalctl, persistence

Two logging stacks coexist on RHEL: traditional rsyslog (text files in /var/log/) and the systemd journal (structured, queryable via journalctl). The exam frequently asks for persistent journal storage.

Key concepts
  • rsyslog: The default logging daemon on RHEL. Configuration in /etc/rsyslog.conf defines rules that route messages by facility (auth, kern, mail, cron) and priority (emerg, alert, crit, err, warning, notice, info, debug) to log files. Most logs go to /var/log/.
  • Key log files: /var/log/messages captures most system messages. /var/log/secure logs authentication events. /var/log/boot.log records boot messages. /var/log/cron logs cron job execution. /var/log/audit/audit.log stores SELinux and auditd events.
  • journalctl filtering: journalctl -u sshd filters by unit. journalctl --since "2026-04-01" --until "2026-04-02" filters by date range. journalctl -p err shows only errors and above. journalctl _PID=1234 filters by process ID. Combine filters for precise log queries.
  • Persistent journal storage: By default, the systemd journal is stored in /run/log/journal (volatile, cleared on reboot). To make it persistent, create /var/log/journal directory and restart systemd-journald, or set Storage=persistent in /etc/systemd/journald.conf.
  • Log rotation: logrotate automatically compresses, rotates, and removes old log files. Configuration in /etc/logrotate.conf and /etc/logrotate.d/. Settings include rotation frequency (daily, weekly), number of old files to keep, compression, and post-rotation scripts.
Concrete example

Task: make the systemd journal persist across reboots. Solution: create the directory + restart journaldmkdir -p /var/log/journal, systemd-tmpfiles --create --prefix /var/log/journal, systemctl restart systemd-journald. Verify with journalctl --disk-usage (should show MB in /var/log/journal/, not /run/log/journal/). Reboot and confirm journalctl --since yesterday still has data.

Key takeaway: persistent journal = create /var/log/journal directory + restart journald. Verify with journalctl --disk-usage.
⚡ Mini-quiz
Drill logging scenarios → quick quiz (5 questions).
Lesson 9.3 systemd timers — the modern cron

systemd timers replace cron with better logging, calendar expressions, and dependency awareness. Every timer is a .timer unit paired with a matching .service unit. The exam may accept either cron or a timer — timers are the modern answer.

Key concepts
  • Two units per timer: A timer needs foo.timer (when) AND foo.service (what). Same base name — systemd pairs them by convention unless Unit= overrides. Both live in /etc/systemd/system/.
  • OnCalendar expressions: OnCalendar=*-*-* 02:30:00 = daily at 02:30. OnCalendar=Mon..Fri 09:00 = weekdays at 9 AM. OnCalendar=hourly / daily / weekly are shortcuts. Test parsing with systemd-analyze calendar "Mon..Fri 09:00".
  • Monotonic timers: OnBootSec=10min = run 10 minutes after boot. OnUnitActiveSec=1h = re-fire 1 hour after the last activation. Useful for "run periodically while the system is up".
  • Persistent & AccuracySec: Persistent=true runs missed jobs after a poweroff window (like anacron). AccuracySec=1m lets systemd batch firings to save power — set to 1us for cron-level precision.
  • Enabling & inspecting: systemctl daemon-reload after writing units, then systemctl enable --now foo.timer (enable the timer, NOT the service). systemctl list-timers shows next-fire times. journalctl -u foo.service shows the actual run output.
Concrete example

Task: replace a cron job that ran /root/backup.sh at 02:30 with a systemd timer. Solution: two unit files + enable the timer — create /etc/systemd/system/backup.service with [Service]\nType=oneshot\nExecStart=/root/backup.sh. Then /etc/systemd/system/backup.timer with [Timer]\nOnCalendar=*-*-* 02:30:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target. systemctl daemon-reload; systemctl enable --now backup.timer. Verify with systemctl list-timers backup.timer.

Key takeaway: two units (timer + service), enable the timer. Persistent=true for catch-up after downtime. systemctl list-timers for verification.
⚡ Mini-quiz
Drill systemd timer scenarios → study mode (10 questions).
💻 Worked scenario — Cron job runs but produces nothing — investigate
Task: 0 2 * * * /usr/local/bin/backup.sh is in root's crontab; the journal shows it fires nightly, but no backup file is created. Sequence: (1) grep CRON /var/log/cron | tail -20 — confirm the job is actually triggered; (2) journalctl -u crond --since '24 hours ago' for daemon errors; (3) mail or cat /var/spool/mail/root — cron mails stderr if there's no redirection; the error is almost always missing $PATH; (4) fix it once for the whole crontab: prepend PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin in crontab -e, and append 2>&1 | logger -t backup to the job line so output lands in journalctl -t backup. Verify: next run produces output; journalctl -t backup --since today shows the trace; the backup file appears.
Key takeaways
  • Per-user cron: crontab -e edits the current user's table. System cron lives in /etc/cron.d/ and includes a username field. Format: m h dom mon dow command.
  • A systemd timer is two units: foo.timer (when) + foo.service (what). Enable with systemctl enable --now foo.timer; inspect with systemctl list-timers.
  • Persistent journal: mkdir -p /var/log/journal (or set Storage=persistent in journald.conf) so journalctl --since yesterday works after a reboot.
⚡ Mini-quiz — Drill cron syntax, systemd-timer pairs, at jobs, and persistent journald configuration.
Quick quiz →
10

Boot Process & Troubleshooting3 lessons

BIOS/UEFI → GRUB2 → kernel + initramfs → systemd target. The single most-tested troubleshooting drill: reset a forgotten root password via rd.break at the GRUB menu. Don't forget touch /.autorelabel — without it, SELinux blocks the new shadow file and login fails.

grub2 grub2-mkconfig initramfs dracut rd.break rescue-target emergency-target autorelabel
~3h
📖 Read in-depth chapter
BIOS/UEFI → GRUB2 → kernel + initramfs → systemd target. The single most-graded troubleshooting drill: reset a forgotten root password via rd.break at the GRUB menu. Don't forget touch /.autorelabel — without it, SELinux blocks the new shadow file and login fails after the reboot.
Lesson 10.1 Boot sequence — firmware, GRUB2, kernel, systemd

Knowing the boot order lets you pick the right intervention point. The exam may ask you to add a kernel parameter, change the default target, or rebuild GRUB after a config change.

Key concepts
  • BIOS/UEFI firmware: The first stage of boot. BIOS uses MBR to locate the bootloader. UEFI reads the GPT and loads the bootloader from the EFI System Partition (ESP, typically mounted at /boot/efi). UEFI supports Secure Boot, which verifies bootloader signatures.
  • GRUB2 bootloader: GRUB2 loads the kernel and initramfs into memory. Configuration is in /boot/grub2/grub.cfg (generated, do not edit directly). Defaults are set in /etc/default/grub. After changes, regenerate with grub2-mkconfig -o /boot/grub2/grub.cfg.
  • Kernel & initramfs: The kernel initializes hardware, mounts the root filesystem (initially from initramfs, a temporary RAM-based filesystem containing essential drivers), then pivots to the real root filesystem. Kernel parameters can be passed via GRUB2.
  • systemd initialization: Once the kernel starts PID 1 (systemd), it reads the default target and starts all units required to reach that target in parallel. multi-user.target provides a full multi-user text environment; graphical.target adds a GUI.
  • Boot targets: systemctl get-default shows the current default target. systemctl set-default multi-user.target changes it. systemctl isolate rescue.target switches to rescue mode (single-user, root shell, minimal services). Targets replace the older SysVinit runlevels.
Concrete example

Task: switch the default boot target from graphical to text-mode (multi-user). Solution: systemctl set-defaultsystemctl set-default multi-user.target. This updates /etc/systemd/system/default.target as a symlink. Verify with systemctl get-default. Reboot to confirm. For a one-time switch without changing the default, systemctl isolate multi-user.target.

Key takeaway: firmware → GRUB2 → kernel + initramfs → systemd → default target. set-default persists; isolate is one-shot.
⚡ Mini-quiz
Drill boot-sequence scenarios → study mode (10 questions).
Lesson 10.2 Boot troubleshooting — rd.break, rescue, emergency

Root password reset is the canonical RHCSA boot-recovery drill. Walk through the procedure in a lab until it's reflex — minutes count, and missing touch /.autorelabel means the relabeled system can't log in.

Key concepts
  • Root password reset (rd.break): At the GRUB2 menu, press e to edit, append rd.break to the linux line, then Ctrl+X to boot. This drops you into initramfs before the real root is mounted. Remount with mount -o remount,rw /sysroot, chroot into /sysroot, run passwd root, then touch /.autorelabel and exit.
  • Emergency & rescue targets: Append systemd.unit=emergency.target to the GRUB2 kernel line for the most minimal environment (root filesystem mounted read-only). Use systemd.unit=rescue.target for single-user mode with more services. Both require the root password.
  • GRUB2 editing at boot: Press e at the GRUB2 menu to edit kernel parameters for the current boot only. Common uses: append rd.break for password reset, systemd.unit=rescue.target for rescue mode, or init=/bin/bash for an emergency shell without systemd.
  • Filesystem repair: If the system fails to boot due to a corrupt filesystem, boot into emergency mode. Run fsck /dev/sdXn to check and repair the filesystem. Fix /etc/fstab errors that prevent mounting. Remount root as read-write with mount -o remount,rw / to make changes.
  • SELinux relabeling: After any rd.break password reset, touch /.autorelabel is mandatory to trigger a full SELinux relabel on the next boot. Without it, the new password file will have the wrong SELinux context and login will fail. The relabel process can take several minutes.
Concrete example

Task: forgotten root password — recover. Solution: rd.break drill — at GRUB2, press e, append rd.break to the linux line, Ctrl+X to boot. At the switch_root:/# prompt: mount -o remount,rw /sysroot, chroot /sysroot, passwd root (set new), touch /.autorelabel, exit, exit. The system reboots, relabels (takes 1-3 minutes), then comes up with the new password. Skip /.autorelabel and login will fail — guaranteed point loss.

Key takeaway: rd.break → remount rw → chroot → passwd → /.autorelabel → reboot. Forgetting the relabel is the single most common exam mistake.
⚡ Mini-quiz
Drill boot-recovery scenarios → quick quiz (5 questions).
Lesson 10.3 GRUB2 configuration & kernel updates

Beyond emergency edits, RHCSA expects you to change persistent kernel parameters, rebuild the initramfs, and protect the bootloader. Every change to /etc/default/grub must be followed by grub2-mkconfig.

Key concepts
  • Editing kernel parameters: Persistent boot parameters live in /etc/default/grub as GRUB_CMDLINE_LINUX="...". After editing, regenerate with grub2-mkconfig -o /boot/grub2/grub.cfg (BIOS) or /boot/efi/EFI/redhat/grub.cfg (UEFI). Alternative: grubby --update-kernel=ALL --args="quiet" applies to every installed kernel without a full regenerate.
  • Rebuilding initramfs (dracut): dracut -f rebuilds the initramfs for the running kernel. Needed after adding drivers, changing root device, or modifying /etc/dracut.conf.d/*.conf. dracut -f /boot/initramfs-$(uname -r).img $(uname -r) for an explicit target.
  • GRUB2 password protection: grub2-setpassword (interactive) sets a bootloader password stored in /boot/grub2/user.cfg. After this, editing GRUB entries at boot requires the password — blocks the unauthenticated rd.break recovery path.
  • Kernel updates: dnf update kernel installs a new kernel alongside the existing one (RHEL keeps the last 3 by default, configurable via installonly_limit in /etc/dnf/dnf.conf). grubby --default-kernel shows the default; grubby --set-default /boot/vmlinuz-... changes it.
  • Verifying the running kernel: uname -r shows the running kernel version. rpm -q kernel lists installed kernel packages. After a kernel update, reboot and re-check uname -r.
Concrete example

Task: add the audit=1 kernel parameter persistently. Solution: edit + mkconfig (or grubby) — edit /etc/default/grub, append audit=1 inside GRUB_CMDLINE_LINUX="...", then grub2-mkconfig -o /boot/grub2/grub.cfg (for BIOS) or the EFI variant. Faster equivalent: grubby --update-kernel=ALL --args="audit=1". Reboot, then verify with cat /proc/cmdline — must contain audit=1.

Key takeaway: never edit grub.cfg directly — change /etc/default/grub and regenerate. grubby is the shortcut. dracut -f after driver or storage changes.
⚡ Mini-quiz
Drill GRUB2 configuration scenarios → study mode (10 questions).
💻 Worked scenario — Reset a forgotten root password with rd.break
Task: locked out — no console root password, no working sudoer. Sequence: (1) reboot; at the GRUB menu press e on the kernel line; (2) append rd.break enforcing=0 to the linux line and press Ctrl-X to boot; (3) at the switch_root:/# prompt: mount -o remount,rw /sysroot && chroot /sysroot; (4) passwd — set a new password; (5) touch /.autorelabel — forces SELinux to relabel /etc/shadow on next boot, otherwise login still fails; (6) exit; exit — the system reboots, relabels, then reboots again. Verify: log in with the new password; getenforce is back to Enforcing; ls -laZ /etc/shadow shows the correct shadow_t context.
Key takeaways
  • The rd.break sequence: edit kernel line at GRUB → add rd.breakmount -o remount,rw /sysrootchroot /sysrootpasswdtouch /.autorelabel → exit, exit.
  • Switch the default target with systemctl set-default multi-user.target (CLI) or graphical.target (GUI); rescue/emergency for repair work.
  • After any GRUB config edit, run grub2-mkconfig -o /boot/grub2/grub.cfg (BIOS) or /boot/efi/EFI/redhat/grub.cfg (UEFI) to regenerate the bootloader file.
⚡ Mini-quiz — Drill the rd.break password reset, target switching, and grub2-mkconfig paths.
Quick quiz →
11

Containers & Automation3 lessons

Podman is the RHCSA's container engine — daemonless, rootless, and integrated with systemd. Pull, run, expose, and persist containers across reboots via systemd user services + lingering. Shell scripting basics for batch automation (user creation loops, config sweeps).

podman rootless containerfile port-mapping volumes quadlet loginctl-linger bash-scripting
~3h
📖 Read in-depth chapter
Podman is the RHCSA's container engine — daemonless, rootless, and integrated with systemd. Pull, run, expose ports, persist data with volumes, and survive reboots via systemd user services with loginctl enable-linger. Plus enough shell-scripting reflex (loops, conditionals, exit codes) to automate the small batch tasks the exam throws at you.
Lesson 11.1 Podman basics — pull, run, lifecycle, rootless

Podman replaces Docker on RHEL. Daemonless, OCI-compatible, and runs rootless by default — the RHCSA expects you to run containers as a regular user, not as root.

Key concepts
  • Podman basics: Podman is a daemonless container engine that runs OCI containers. Unlike Docker, it does not require a running daemon. podman pull registry.access.redhat.com/ubi9/ubi downloads an image. podman images lists local images. Podman commands mirror Docker syntax.
  • Running containers: podman run -d --name myapp -p 8080:80 httpd runs a container in detached mode (-d), with a name (--name), mapping host port 8080 to container port 80 (-p). podman ps lists running containers. podman ps -a includes stopped containers.
  • Container lifecycle: podman stop myapp sends SIGTERM then SIGKILL after a timeout. podman start myapp restarts a stopped container. podman rm myapp removes a container. podman rmi image-id removes an image. podman exec -it myapp /bin/bash opens a shell inside a running container.
  • Rootless containers: Podman runs containers as a regular user without root privileges. Rootless containers use user namespaces for isolation. This is a key security advantage over Docker and is the expected mode on the RHCSA exam. Rootless containers cannot bind to ports below 1024.
  • Building images: A Containerfile (equivalent to Dockerfile) defines the image build. FROM ubi9/ubi sets the base image. RUN dnf install -y httpd runs a command during build. EXPOSE 80 documents the port. podman build -t myimage . builds the image from the Containerfile.
Concrete example

Task: as user admin (not root), pull ubi9/httpd-24 and run it on host port 8080. Solution: rootless run with -p — as admin: podman pull registry.access.redhat.com/ubi9/httpd-24, podman run -d --name web -p 8080:8080 registry.access.redhat.com/ubi9/httpd-24. Note: rootless can't bind below 1024, so target port 8080 is correct. Test with curl localhost:8080. Open the firewall: sudo firewall-cmd --add-port=8080/tcp --permanent && sudo firewall-cmd --reload.

Key takeaway: rootless by default. Can't bind < 1024 — use 8080-style ports. podman ps -a includes stopped; podman rm only deletes stopped.
⚡ Mini-quiz
Drill Podman basics scenarios → study mode (10 questions).
Lesson 11.2 Container persistence — volumes + systemd integration

Containers vanish on reboot unless wired into systemd. The RHCSA tests both volume mounts (data survives container deletion) and systemd user-services for auto-start.

Key concepts
  • Bind mounts & named volumes: -v /host/path:/container/path:Z mounts a host directory; the :Z relabels for SELinux private use. Named volumes: podman volume create mydata, then -v mydata:/var/lib/data. Volumes survive podman rm; the container does not.
  • SELinux container labels: Bind mounts on RHEL need :Z (private to one container) or :z (shared across containers) to set the container_file_t label. Skipping this causes "permission denied" inside the container even when host permissions look fine.
  • podman generate systemd: podman generate systemd --new --name web --files emits a container-web.service unit. The --new flag makes the unit recreate the container each start (safer than reattaching to a stopped one).
  • User services + lingering: Drop the generated unit into ~/.config/systemd/user/, then systemctl --user daemon-reload && systemctl --user enable --now container-web. By default, user services stop at logout — loginctl enable-linger admin keeps them running after logout and across reboots.
  • Quadlet (RHEL 9+): The modern alternative — drop a .container file into ~/.config/containers/systemd/, and systemd auto-generates the service on daemon-reload. Less ceremony than podman generate systemd; preferred direction going forward.
Concrete example

Task: run an httpd container as user admin with /home/admin/www serving content, surviving reboot. Solution: volume mount + systemd user service + lingerpodman run -d --name web -p 8080:8080 -v /home/admin/www:/var/www/html:Z registry.access.redhat.com/ubi9/httpd-24. Generate the unit: mkdir -p ~/.config/systemd/user; cd ~/.config/systemd/user; podman generate systemd --new --name web --files. systemctl --user daemon-reload, systemctl --user enable --now container-web. As root: loginctl enable-linger admin. Reboot — container comes back automatically.

Key takeaway: volumes for data persistence (with :Z for SELinux). podman generate systemd --new + --user + loginctl enable-linger for reboot-survival.
⚡ Mini-quiz
Drill container persistence scenarios → quick quiz (5 questions).
Lesson 11.3 Shell scripting for sysadmin automation

The RHCSA doesn't require advanced Bash, but you must be able to write loops that batch-create users, conditionals that check service state, and scripts that run cleanly under cron.

Key concepts
  • Bash script structure: Start with #!/bin/bash (the shebang). Make scripts executable with chmod +x script.sh. Use variables (NAME="server1"), command substitution (DATE=$(date +%F)), and meaningful comments. Always set set -e to exit on errors in production scripts.
  • Conditionals: if [ -f /path/file ]; then ... fi tests if a file exists. if [ "$VAR" = "value" ]; then ... elif ... else ... fi handles multiple conditions. Use [[ ]] for pattern matching and regex. Common test operators: -d (directory), -r (readable), -z (empty string), -eq (numeric equal).
  • Loops: for user in alice bob charlie; do useradd $user; done iterates over a list. for i in $(seq 1 10); do ... done iterates over numbers. while read line; do ... done < file.txt processes a file line by line. Loops are essential for batch user creation and configuration tasks.
  • Exit codes: Every command returns an exit code: 0 = success, non-zero = failure. $? holds the last command's exit code. Use exit 0 for success and exit 1 for failure in your scripts. && runs the next command only if the previous succeeded; || runs it only if the previous failed.
  • Cron + scripts: Combine shell scripts with cron for automated administration: backup scripts, log cleanup, user provisioning, system health checks. Example: 0 2 * * * /root/scripts/backup.sh >> /var/log/backup.log 2>&1 runs a backup at 2 AM daily and logs output.
Concrete example

Task: read /root/users.txt (one username per line) and create each user with a random initial password. Solution: while-read loop + useradd + openssl — script: #!/bin/bash\nset -e\nwhile read user; do\n [ -z "$user" ] && continue\n useradd -m "$user"\n pw=$(openssl rand -base64 12)\n echo "$user:$pw" | chpasswd\n echo "$user $pw" >> /root/credentials.txt\ndone < /root/users.txt. chmod +x and run. Verify with id alice. Lock /root/credentials.txt with chmod 600 immediately.

Key takeaway: shebang, set -e, absolute paths, redirect both streams. while read for file-driven loops; for user in for inline lists.
⚡ Mini-quiz
Drill shell-scripting scenarios → study mode (10 questions).
💻 Worked scenario — Persist a rootless podman container across reboot
Task: run a rootless postgres:16 container as user pgops, with /home/pgops/pgdata persisted, auto-starting on boot even when nobody is logged in. Sequence: (1) as pgops: podman run -d --name pg -v /home/pgops/pgdata:/var/lib/postgresql/data:Z -e POSTGRES_PASSWORD=secret postgres:16; (2) podman generate systemd --new --files --name pg — produces container-pg.service; (3) install the unit: mkdir -p ~/.config/systemd/user && mv container-pg.service ~/.config/systemd/user/; (4) systemctl --user daemon-reload && systemctl --user enable container-pg.service; (5) as root: loginctl enable-linger pgops — required for user services to start without an active session. Verify: reboot; machinectl shell pgops@ then systemctl --user status container-pg shows active (running); podman ps shows the container; psql connects.
Key takeaways
  • Rootless Podman = run as a normal user; combine with loginctl enable-linger <user> so the user's services keep running after logout.
  • Generate a systemd user unit from a running container with podman generate systemd --name <ctn> --new --files; place under ~/.config/systemd/user/ and enable with systemctl --user enable --now <name>.
  • Bash script essentials: #!/bin/bash shebang, set -euo pipefail safety, for var in list; do ... done, and $? to read the previous exit code.
⚡ Mini-quiz — Drill rootless Podman, systemd-user lingering, port mapping, volumes, and bash scripting.
Quick quiz →

Capstone labs

Four labs that exercise the modules end-to-end. Run each in a RHEL 9, Rocky Linux 9, or AlmaLinux 9 VM (libvirt, VirtualBox, or a cloud free tier) and tear it down when done. These are the patterns the exam recurs on — building them once burns the muscle memory.

Lab 1 — Storage stack: partition, LVM, XFS, fstab

Take a fresh 10 GB virtual disk. Create one GPT partition with parted, mark it LVM-flagged. pvcreate on the partition, vgcreate datavg, lvcreate -n datalv -L 4G datavg. Format XFS, mount at /srv/data, persist via UUID in /etc/fstab, verify with mount -a + reboot. Then add a second disk, vgextend, and lvextend -r -L +2G to grow the LV and filesystem in one shot.

Lab 2 — Multi-user collaborative directory

Create users alice, bob, carol; create group team; usermod -aG team each (with -a!). Make /shared/team, chgrp team, chmod 2770 (SGID + group rwx). Set default ACLs so new files inherit group rw: setfacl -m d:g:team:rw /shared/team. Verify by touching a file as alice and confirming with getfacl + ls -l that the group is team with group-rw.

Lab 3 — Web service hardening (httpd + firewalld + SELinux)

dnf -y install httpd; deploy content under /srv/www; set the SELinux file context: semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?" && restorecon -Rv /srv/www. Open the firewall: firewall-cmd --add-service=http --permanent && firewall-cmd --reload. Set the SELinux boolean if httpd connects out: setsebool -P httpd_can_network_connect on. systemctl enable --now httpd. Verify with curl localhost and ls -Z /srv/www.

Lab 4 — Boot recovery drill

Forget the root password. At GRUB2, press e, append rd.break, Ctrl+X. At the initramfs prompt: mount -o remount,rw /sysroot, chroot /sysroot, passwd root, touch /.autorelabel, exit twice. Confirm login works after the relabel. Then set a GRUB password to block this on future reboots: grub2-setpassword, verify by trying e at GRUB — it now demands credentials.

Top 4 mistakes candidates make on RHCSA

  • fstab entries that don't survive reboot: using device names instead of UUIDs, or skipping mount -a verification before committing. A broken fstab can stop the next boot — fix it before you ever reboot.
  • usermod -G without -a: silently wipes a user's other supplementary groups. Always usermod -aG newgroup user and verify with id user.
  • Ignoring SELinux denials: when a service "looks configured but won't work", check ausearch -m avc -ts recent. Don't disable SELinux — the exam zero-grades it. Use restorecon, semanage, or a boolean.
  • Forgetting firewall-cmd --reload after --permanent: permanent rules don't take effect until reload. Always --permanent && reload, then verify with firewall-cmd --list-all.

Ready for RHCSA?

Scenario-based practice questions across every EX200 objective. Free, no signup, instant feedback on every answer. Open the Cert Quest path to combine practice with mini-game drills.

Build the Linux career stack

RHCSA is the floor. From here, the natural follow-ons are Linux+ as a vendor-neutral comparison, Docker for containers, and CKA once you move from VMs to Kubernetes.

Start practicing →