Red Hat Certified System Administrator (RHCSA) #16 Full-Length Practice Exam — 16 Tasks with Solutions

18 min read

From #1 the exam introduction through #15 exam tips, we have circled every RHCSA domain once. The final post of this series is not one you read but one you solve. Just like the real EX200, it gathers 16 tasks that integrate every domain in one place. These are not multiple-choice questions — they are hands-on scenarios where you create users, extend LVM, and fix SELinux contexts at an empty shell, and each task carries a point value.

The recommended time limit is 2.5 hours, the same as the real exam. The pass line is 210/300 (70%), scored by summing the point values of all 16 tasks. If you get stuck on a task, mark it, move on, and bank points from the high-value tasks you’re confident about first — that is the way over the pass line.

The most common cause of lost points in RHCSA is configuration that disappears after a reboot. Mounts must go into fstab, services must be enabled, and the network must persist as a NetworkManager configuration. When you solve this mock too, reboot once after finishing every task and verify directly that your work survives. Solve each task fully on your own first, then unfold the solution. If you read the solution first, your hands never learn it.

How to take it #

  1. Spinning up RHEL 9 or a compatible distribution (AlmaLinux, Rocky Linux) as a VM and solving directly on it is closest to the real thing. For the storage tasks, add one or two empty data disks (such as /dev/vdb) to the VM. For the NFS mount task, either spin up a separate NFS server on the same network or follow the alternative path given inside the task.
  2. Don’t skip the persistence each task requires. Mounts must go into fstab, services into systemctl enable, and the network into an nmcli connection profile to satisfy the scoring criteria. If you only run something and skip making it permanent, it scores 0 on the reboot check.
  3. Solve all 16 tasks to the end, then unfold the solutions and grade yourself in one pass. Peeking at solutions mid-exam dulls your sense of the real thing. Working with only man pages for reference brings you closer to the actual exam environment.
  4. Once you finish every task, always reboot once and confirm on the rebooted system that each task’s result survives. Whether mounts attach automatically, services start automatically, and the network configuration is still alive is the final gate that decides whether you pass RHCSA.

Domain distribution #

The 16 tasks are arranged to match the domain weights of the real EX200. Storage and file systems carry the most tasks, making them the core domains that determine whether you pass.

#DomainTasksTask numbers
1Essential tools and shell scripting21, 2
2Boot and system operations33, 4, 5
3Storage and file systems46, 7, 8, 9
4Packages and networking210, 11
5Users and security312, 13, 14
6Containers215, 16

The point values reflect the domain weights and task difficulty, totaling 300. The scoring criteria are laid out at the end of the post.


Task 1 (15 points): Essential tools and shell scripting #

Under /etc, find all files whose names end in .conf and save the list to /root/conf-list.txt, one per line. Then, from this list, select only the lines that contain the string network and save them to /root/conf-network.txt.

Solution

Find the files with find and save the list.

find /etc -type f -name '*.conf' > /root/conf-list.txt

Pull only the lines containing the string from the saved list.

grep network /root/conf-list.txt > /root/conf-network.txt

Explanation: find’s -name '*.conf' must be quoted so the shell doesn’t expand the wildcard first. Redirecting standard output with > saves the result to a file instead of printing to the screen, and a second filter with grep network produces the second file. Working as root keeps permission-denied messages out of the output.

Task 2 (15 points): Essential tools and shell scripting #

Write the script /root/bin/diskcheck.sh. It takes a single directory path as an argument; if the directory exists, it prints the directory’s total usage in a human-readable unit and exits with code 0. If there is no argument or the directory does not exist, it prints a usage message to standard error and exits with code 1. After writing it, grant execute permission.

Solution

Write the script.

#!/usr/bin/bash
if [ -z "$1" ] || [ ! -d "$1" ]; then
    echo "Usage: $0 <directory>" >&2
    exit 1
fi
du -sh "$1"
exit 0

Grant execute permission and check the behavior.

mkdir -p /root/bin
chmod +x /root/bin/diskcheck.sh
/root/bin/diskcheck.sh /etc
/root/bin/diskcheck.sh ; echo $?

Explanation: [ -z "$1" ] checks whether the argument is empty and [ ! -d "$1" ] checks whether it is not a directory; joining the two with || treats either condition being true as an error. Always wrap variables in double quotes to guard against paths that contain spaces, and send the usage message to standard error with >&2. For du -sh, -s gives the total and -h gives a human-readable unit. Since the exit code is a scoring item, always make it explicit with exit.


Task 3 (15 points): Boot and system operations #

Assume you have forgotten the root password. Reboot the system, enter rescue mode, reset the root password to Redhat123, take action so the SELinux labels are not broken, and return to a normal boot.

Solution

At boot, edit the kernel line (the line starting with linux) in the GRUB2 menu with e, append rd.break at the end, and boot with Ctrl+x. Once you are in the switch_root shell, do the following.

mount -o remount,rw /sysroot
chroot /sysroot

passwd root
# Enter Redhat123 as the new password

touch /.autorelabel
exit
exit

Explanation: rd.break stops at the initramfs stage, so the real root is mounted read-only at /sysroot. You therefore have to make it writable with remount,rw and enter it with chroot for passwd to take effect on the actual system. Failing to create /.autorelabel is the most common trap: the SELinux context goes out of sync, the /etc/shadow update is ignored, and login is blocked on the next boot. After autorelabel, the system reboots a second time to complete the relabeling.

Task 4 (15 points): Boot and system operations #

Change the system’s default boot target from graphical mode to multi-user (text) mode. After the change, confirm what the current default target is, and this setting must survive a reboot.

Solution

Set and confirm the default target.

systemctl set-default multi-user.target
systemctl get-default

Explanation: set-default re-points the /etc/systemd/system/default.target symlink to the new target, so the change is permanent and survives a reboot. To switch the current session to that mode immediately, add systemctl isolate multi-user.target, but if the requirement is only to change the default, set-default alone is enough. Using only isolate and skipping set-default is a trap — the system returns to the original mode on reboot.

Task 5 (15 points): Boot and system operations #

Configure a job that runs /usr/local/bin/backup.sh every day at 3:30 a.m. as a systemd timer. Assume the script already exists and is executable. Create the service and timer units, enable the timer, then check the next scheduled run time.

Solution

Create the service unit and the timer unit.

cat > /etc/systemd/system/backup.service <<'EOF'
[Unit]
Description=Daily backup job

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
EOF
cat > /etc/systemd/system/backup.timer <<'EOF'
[Unit]
Description=Run backup daily at 03:30

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

Enable the timer and check the scheduled time.

systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers backup.timer

Explanation: A timer automatically triggers the service of the same name (backup.timerbackup.service), so the service only needs Type=oneshot to define the job. Put [Install] and WantedBy=timers.target on the timer side only, and turn it on with enable --now to make it start automatically at boot — the key trap is that you enable the timer, not the service. Persistent=true catches up on any missed run after boot if the system was off at the scheduled time. If you prefer cron, you can also put 30 3 * * * /usr/local/bin/backup.sh in crontab -e.


Task 6 (20 points): Storage and file systems #

On the empty disk /dev/vdb, create a volume group vgdata, and inside it create a 500MiB logical volume lvweb. Format this logical volume as XFS, mount it at the /web directory, and register it in fstab so it mounts automatically after a reboot.

Solution

Create the physical volume, volume group, and logical volume in order.

pvcreate /dev/vdb
vgcreate vgdata /dev/vdb
lvcreate -L 500M -n lvweb vgdata

Format it as XFS and create the mount point.

mkfs.xfs /dev/vgdata/lvweb
mkdir /web

Register it in fstab by UUID, then mount to verify.

blkid /dev/vgdata/lvweb
echo 'UUID=<the UUID confirmed above> /web xfs defaults 0 0' >> /etc/fstab
mount -a
df -h /web

Explanation: The LVM creation order is pvcreate → vgcreate → lvcreate, and the logical volume device path takes the form /dev/<vg>/<lv> or /dev/mapper/<vg>-<lv>. Using the UUID rather than the device name in fstab is safer, and mount -a catches fstab syntax errors before a reboot. If there is a typo in fstab, the next boot drops into emergency mode, so always confirm that mount -a finishes without error right after adding the entry.

Task 7 (20 points): Storage and file systems #

Extend the logical volume lvweb created in Task 6 to 800MiB, and grow the XFS file system on top of it so it uses the full extended size while staying mounted. The volume group cannot be extended if it lacks free space, so check the free space first.

Solution

Check the volume group’s free space and extend the logical volume.

vgs vgdata
lvextend -L 800M /dev/vgdata/lvweb

Grow the XFS file system online.

xfs_growfs /web
df -h /web

Explanation: Extending a logical volume with lvextend only grows the size at the metadata level; a separate file system extension is needed for the extra space to be usable. The key difference is that XFS uses xfs_growfs (taking the mount point as the argument) while ext4 uses resize2fs (taking the device as the argument), and XFS can only grow, not shrink. You can also handle both in one shot with lvextend -r -L 800M. If there isn’t enough free space, lvextend fails, so check the free space with vgs first.

Task 8 (20 points): Storage and file systems #

Encrypt the empty disk /dev/vdc with LUKS. Use the passphrase Secret123, and map it to the name cryptdata when unlocking. Format the unlocked device as ext4, mount it at /secure, and register it in crypttab and fstab so it asks for the passphrase at boot and unlocks and mounts automatically.

Solution

Initialize the disk with LUKS and open the lock.

cryptsetup luksFormat /dev/vdc
# Enter YES, then set the passphrase Secret123

cryptsetup luksOpen /dev/vdc cryptdata

Format and mount the mapped device.

mkfs.ext4 /dev/mapper/cryptdata
mkdir /secure
mount /dev/mapper/cryptdata /secure

Register it in crypttab and fstab.

blkid /dev/vdc
echo 'cryptdata UUID=<the LUKS UUID of vdc> none' >> /etc/crypttab
echo '/dev/mapper/cryptdata /secure ext4 defaults 0 0' >> /etc/fstab
mount -a

Explanation: Making LUKS persistent requires two steps. /etc/crypttab unlocks /dev/vdc as cryptdata at boot (a third field of none means it prompts for the passphrase at boot), and /etc/fstab mounts the resulting /dev/mapper/cryptdata. The most common trap is that the UUID in crypttab must be the UUID of the encrypted source device (/dev/vdc), not the mapped device. If you skip crypttab, there is no mapping at boot and the fstab mount fails.

Task 9 (20 points): Storage and file systems #

The NFS server nfs.example.com is exporting /exports/shared. Configure this share with AutoFS so it mounts automatically only when /mnt/shared is accessed. Install the AutoFS package, write the map files, then enable the service.

Solution

Install the package and register the mount point in the master map.

dnf install -y autofs
echo '/mnt /etc/auto.shared' >> /etc/auto.master.d/shared.autofs

Write the direct map file.

echo 'shared -rw nfs.example.com:/exports/shared' > /etc/auto.shared

Enable the service and confirm the automatic mount by accessing it.

systemctl enable --now autofs
ls /mnt/shared
mount | grep shared

Explanation: AutoFS uses a two-level structure: you register a “mount parent path + map file” in the master map (/etc/auto.master or auto.master.d/*.autofs), and write the “sub key + options + remote path” in the map file. Putting /mnt in the master map and shared as the key in the map file produces the mount point /mnt/shared; you don’t need to create that directory yourself (AutoFS manages it). The service must be enabled with enable --now to survive a reboot, and if NFS client utilities are missing, dnf install nfs-utils is also needed.


Task 10 (15 points): Packages and networking #

From the postgresql module streams, enable and install version 15. After installing, confirm that the enabled stream is 15.

Solution

Check the available streams and install version 15.

dnf module list postgresql
dnf module install -y postgresql:15

Confirm the enabled stream.

dnf module list postgresql --enabled

Explanation: AppStream modules provide multiple versions of the same software as streams, and dnf module install postgresql:15 enables stream 15 and installs the default profile. Only one stream per module can be enabled at a time, so if another version is already active, reset it with dnf module reset postgresql before installing the stream you want. Seeing [e] next to 15 in the --enabled list confirms it is correct.

Task 11 (15 points): Packages and networking #

Set the static IPv4 address 192.168.50.20/24, gateway 192.168.50.1, and DNS 192.168.50.1 on the default network connection. Apply the configuration permanently with nmcli, activate the change, then confirm the applied result. Assume the connection name is System eth0.

Solution

Confirm the connection name and set the static address.

nmcli connection show
nmcli connection modify "System eth0" \
  ipv4.addresses 192.168.50.20/24 \
  ipv4.gateway 192.168.50.1 \
  ipv4.dns 192.168.50.1 \
  ipv4.method manual

Apply the change and check the result.

nmcli connection up "System eth0"
ip addr show
nmcli connection show "System eth0" | grep ipv4

Explanation: nmcli connection modify saves the connection profile to disk permanently, so the configuration survives a reboot. If you don’t change ipv4.method to manual, DHCP keeps running even after you set a static address and the intended IP is never assigned — a common trap. The modification alone doesn’t take effect immediately, so you must reactivate it with connection up (or nmcli device reapply), then verify with ip addr that the address is actually on the interface.


Task 12 (20 points): Users and security #

Create the group devteam, and create the users alice and bob adding them as supplementary group members of this group. Grant alice full sudo privileges without a password, and set an expiry policy on bob’s account so the password must be changed every 90 days.

Solution

Create the group and users.

groupadd devteam
useradd -G devteam alice
useradd -G devteam bob
passwd alice
passwd bob

Grant alice passwordless sudo privileges.

echo 'alice ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/alice
visudo -cf /etc/sudoers.d/alice

Set the maximum password age for bob.

chage -M 90 bob
chage -l bob

Explanation: A supplementary group is set with useradd -G at creation time (for an existing user, use usermod -aG to preserve the existing groups); the trap is that usermod -G without -a wipes out all existing supplementary groups. For sudo privileges, placing a file under /etc/sudoers.d/ is safer than editing /etc/sudoers directly, and you must check the syntax with visudo -cf. chage -M 90 sets the maximum password age to 90 days, and -l confirms the policy.

Task 13 (20 points): Users and security #

Create the directory /srv/project, and set an ACL so the group devteam can read, write, and execute in this directory. Also set a default ACL so that files and directories newly created in this directory later automatically have the same permissions for devteam.

Solution

Create the directory and set the ACL and the default ACL.

mkdir -p /srv/project
setfacl -m g:devteam:rwx /srv/project
setfacl -m d:g:devteam:rwx /srv/project

Confirm the ACL that was set.

getfacl /srv/project

Explanation: setfacl -m g:devteam:rwx sets a group ACL on the directory itself, while an entry with the d: (default) prefix sets the permissions that files and directories newly created later inside that directory will inherit. The current entries and the default entries are independent, so you must set both to satisfy “access now” and “access for things created later.” Verify the default ACL by checking whether default: lines appear in the getfacl output.

Task 14 (20 points): Users and security #

You configured a web server to serve content from the non-standard directory /srv/www, but access is denied. With SELinux in Enforcing state, give this directory the correct context permanently to resolve the problem. Also, permanently turn on the SELinux boolean that serves user home directories over the web.

Solution

Register a permanent rule for the web content context on the directory and apply it.

semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
restorecon -Rv /srv/www

Turn the boolean on permanently.

setsebool -P httpd_enable_homedirs on

Confirm.

ls -Zd /srv/www
getsebool httpd_enable_homedirs

Explanation: Fixing a context permanently is a two-step process. semanage fcontext -a adds a permanent rule to the policy database, and restorecon labels the actual files according to that rule. Using only chcon works for now but is reverted by restorecon or a relabel, so the trap is that it is not permanent. A boolean must be set with setsebool -P so it is written to disk and survives a reboot; turning it on without -P is temporary. The (/.*)? regex covers the directory itself and every path inside it.


Task 15 (20 points): Containers #

Pull the nginx image from the Red Hat registry (or docker.io), and run a container mynginx that mounts the host’s /srv/web-content read-only at the container’s /usr/share/nginx/html and maps host port 8080 to container port 80 — running it not as root but as the regular user alice.

Solution

Log in as alice, then pull the image and run the container.

su - alice

mkdir -p ~/web-content
podman pull docker.io/library/nginx
podman run -d --name mynginx \
  -p 8080:80 \
  -v ~/web-content:/usr/share/nginx/html:Z,ro \
  nginx

Check the status.

podman ps
curl http://localhost:8080

Explanation: Rootless Podman works as-is in a regular user session, so the key is to enter that user’s environment with su - alice before running it. The :Z on -v tells SELinux to apply a container label (container_file_t) to the host directory, preventing access denial; :ro makes the mount read-only. Rootless containers cannot directly bind to host ports below 1024, which is why the example uses 8080 on the host side.

Task 16 (20 points): Containers #

Configure the mynginx container from Task 15 with quadlet so it runs as a systemd service that starts automatically at boot — that is, even without alice being logged in. Also turn on linger so the service persists even when alice logs out.

Solution

Clean up the existing container and create the unit file in alice’s quadlet directory.

su - alice

podman rm -f mynginx
mkdir -p ~/.config/containers/systemd

cat > ~/.config/containers/systemd/mynginx.container <<'EOF'
[Container]
Image=docker.io/library/nginx
ContainerName=mynginx
PublishPort=8080:80
Volume=%h/web-content:/usr/share/nginx/html:Z,ro

[Service]
Restart=always

[Install]
WantedBy=default.target
EOF

Generate and start the unit.

systemctl --user daemon-reload
systemctl --user enable --now mynginx.service
systemctl --user status mynginx.service

Turn on linger from root so it persists even when alice logs out.

exit
loginctl enable-linger alice

Explanation: With quadlet, placing a ~/.config/containers/systemd/*.container file causes the systemd generator to automatically convert it into a mynginx.service unit; after daemon-reload, you manage that service with --user. Note that a quadlet file named mynginx.container produces a service named mynginx.service. A rootless user service runs only while that user is logged in by default, so loginctl enable-linger is absolutely required for automatic start at boot and persistence after logout.


Scoring criteria #

Grade by summing each task’s points. The total is 300, and 210 or higher is the passing zone.

DomainTasks , pointsSubtotal
Essential tools and shell scripting1(15) , 2(15)30
Boot and system operations3(15) , 4(15) , 5(15)45
Storage and file systems6(20) , 7(20) , 8(20) , 9(20)80
Packages and networking10(15) , 11(15)30
Users and security12(20) , 13(20) , 14(20)60
Containers15(20) , 16(20)40
Total(total)300

Grading is result-based, just like the real exam. It checks not how you typed the commands but whether the final state of the users, volumes, mounts, and services you created matches the requirements. In particular, because the system is rebooted right before grading, a task that skipped persistence scores 0 even if it ran successfully. Even within a single task, partial credit is awarded item by item — permissions, size, context, options — so when you’re stuck on one item, completing the parts you can is still better for your score.

Reviewing weak domains #

After grading, go back to the corresponding post in the table below for any domain where you scored low.

DomainRelated tasksPosts to review
Essential tools and shell scripting1, 2#2 , #3
Boot and system operations3, 4, 5#4 , #9
Storage and file systems6, 7, 8, 9#5 , #6 , #7
Packages and networking10, 11#8 , #10
Users and security12, 13, 14#11 , #12 , #13
Containers15, 16#14

If you ran short on time on a particular task, it may be a matter of execution speed rather than domain knowledge. In that case, re-read #15 exam tips and time management and work through the same 16 tasks again against the clock. Once LVM extension, LUKS, and SELinux contexts are in your muscle memory, the time per task drops noticeably.

Closing the series #

Starting from the exam introduction in #1, we worked through every RHCSA domain across 16 posts — shell tools, scripting, boot, systemd, LVM, LUKS, file systems, NFS, packages, networking, users, sudo, ACL, firewalld, SELinux, and Podman. If you cleared 210 points on this mock, you have built the hands-on ability to clear the pass line in the real exam room. Congratulations.

If RHCSA was the hands-on exam for an RHEL system administrator, the next step is RHCE (EX294), which takes that same hands-on work and automates it with Ansible. Since holding RHCSA is a prerequisite for RHCE, I’d encourage you to carry the momentum of passing straight into the next challenge.

X