RHCSA Sample Exam

Nov 26, 2025 [Jan 9, 2026]

This sample exam is similar to Sander van Vugt’s, but should be different enough that I’m not infringing on his copyright by sharing. I did find quite a few errors in his grading scripts. I’m assuming those were a product of releasing a lot of slightly different versions of the material over the years without fully updating the scripts. But, who knows, maybe fixing the scripts was an implied additional task. In any case, I’ll cut him some slack because I found his videos to be generally excellent.

Update

I easily passed the RHCSA with a perfect score on the first attempt. I can confirm that if you can pass this practice exam within a reasonable amount of time, you are thoroughly prepared.

However, one part of the initial setup did not go as planned. I outlined some sample “essential configurations” here. One of those configurations, and I would consider it to be the most important by far, is swapping caps-lock and escape. After many years using vim and the shell in vi-mode with caps-lock and escape swapped, it’s basically a necessity for me. First, I applied the caps:swapescape option with gsettings in the main testing environment to no avail. Next, I tried creating custom key mappings using showkey and loadkeys from inside the virtual machines. This produced a weird situation where the escape and caps-lock keys were BOTH escape and caps-lock. I didn’t have time to fiddle any further, so I just carried on pressing escape (the physical caps-lock key) twice every time I needed it.

Initial Setup

Installation

  1. Install RHEL 10 on two virtual machines using the “Server with GUI” software selection
  2. Use the default partitioning scheme
  3. Leave the root account disabled, only create an administrative user

Ensure that your network is set up such that the virtual machines are not isolated. Each of them should be assigned a unique IP address.

Initialization

  1. Set the hostnames to server1.myorg.foo and server2.myorg.foo
  2. Set up hostname resolution on both servers
  3. Create an additional administrative user, admin, with the password ‘pass’

Recover root Access

  • Assume that you need to perform emergency troubleshooting/recovery operations on server2 and there is no root password set, set a root password.
Solution

Add init=/bin/bash to grub boot command and boot into minimal shell.

mount -o remount,rw /
passwd root
touch /.autorelabel
echo s > /proc/sysrq-trigger
echo b > /proc/sysrq-trigger

Bootloader Configuration

Both servers:

  1. Modify the system bootloader to show the text details during the boot process for the current default kernel only
  2. Modify the system bootloader to show the text details during the boot process for all kernels including those installed in the future
Solution
  1. Remove rhgb quiet from grub file in /boot/loader/entries/
  2. In /etc/default/grub:
    • Set GRUB_ENABLE_BLSCFG=false
    • Remove rhgb quiet from the GRUB_CMDLINE_LINUX parameter

Default Target

  • Ensure that both servers boot into a multi-user target by default
Solution
  • systemctl set-default multi-user.target

Logging Configuration

Both servers:

  1. Ensure that journal entries are persistent
  2. Create an entry in rsyslog that writes all messages with a severity of error or higher to /var/log/error
  3. Ensure that /var/log/error is rotated on a weekly basis and the last 6 logs are kept before they are rotated out. Also ensure the rotated logs are compressed in storage.
Solution
  1. Ensure that storage is set to auto or persistent in /usr/lib/systemd/journald.conf or drop-ins in /etc/systemd/journald.d
    • Create /var/log/journal
    • Flush journal with systemctl restart systemd-journal-flush.service
    • Ensure that /var/log/journal is not empty
  2. In /etc/rsyslog.conf or /etc/rsyslog.d/error.conf, add *.err /var/log/error
  3. /etc/logrotate.d/error
    /var/log/error {
        weekly
        rotate 6
        compress
    }

Networking

  • Add an additional IPv4 address to both servers
Solution
  • Use nmtui

Managing Tuning Profiles

  1. Configure server1 to use the powersave tuned profile
  2. Configure server2 to use the balanced tuned profile
Solution
  1. tuned-adm profile powersave
  2. tuned-adm profile balanced

Managing SSH Access

  • Set up key-based authentication which allows the root user to login from server1 to server2 without a password.
  • Ensure that root login is permitted on server2 via SSH by key only.
Solution

server2 - permit password login for root:

  • Modify /etc/ssh/sshd_config to include PermitRootLogin Yes
  • Restart sshd

server1 - copy ssh key to server2

ssh-keygen
ssh-copy-id root@server2

server2 - permit ssh authentication via key only

  • Modify /etc/ssh/sshd_config to include PermitRootLogin prohibit-password
  • Restart sshd

Configure Repository Access

  1. Use scp to copy the installation disk from the virtual machine host to server1.
  2. Move the installation disk to /repo.iso and mount it persistently to /repo. Verify that the directories /repo/BaseOS and /repo/AppStream are present.
  3. Install Apache web server and provide http access to the repositories by creating symbolic links to /repo/BaseOS and /repo/AppStream in the default Apache document root.
  4. Configure server2 as a repository client to this server
Solution
  1. virtual machine host:
    scp rhel-10.0-x86_64-dvd.iso user@server1:~/
  2. server1:
    mv /home/user/rhel-10.0-x86_64-dvd.iso /repo.iso
    mkdir /repo
    echo "/repo.iso /repo iso9660 defaults 0 0" >> /etc/fstab
    systemctl daemon-reload
    mount -a
    dnf config-manager --add-repo 'file:///repo/BaseOS'
    dnf config-manager --add-repo 'file:///repo/AppStream'
    echo "gpgcheck=0" >> /etc/yum.repos.d/repo_BaseOS
    echo "gpgcheck=0" >> /etc/yum.repos.d/repo_AppStream
    dnf install httpd
    systemctl enable --now httpd
    firewall-cmd --add-service http --permanent
    firewall-cmd --reload
    ln -s /repo/BaseOS /var/www/html/BaseOS
    ln -s /repo/AppStream /var/www/html/AppStream
  3. server2:
    dnf config-manager --add-repo 'http://server1.myorg.foo/BaseOS'
    dnf config-manager --add-repo 'http://server1.myorg.foo/AppStream'
    echo "gpgcheck=0" >> /etc/yum.repos.d/server1.myorg.foo_BaseOS
    echo "gpgcheck=0" >> /etc/yum.repos.d/server1.myorg.foo_AppStream

Archiving

server1:

  1. Create an archive, /root/archives/archive.tar, containing the /var/log and /etc directories.
  2. Create a compressed archive using gzip, /root/archives/archive.tar.gz, containing the /var/log and /etc directories.
  3. Create a compressed archive using bzip2, /root/archives/archive.tar.bz2, containing the /var/log and /etc directories.
  4. Create a compressed archive using xz, /root/archives/archive.tar.xz, containing the /var/log and /etc directories.
Solution
  1. tar -cf ~/archives/archive.tar /var/log /etc
  2. tar -czf ~/archives/archive.tar.gz /var/log /etc
  3. tar -cjf ~/archives/archive.tar.bz2 /var/log /etc
  4. tar -cJf ~/archives/archive.tar.xz /var/log /etc

Managing Partitions

  • Increase the size of the primary disk for server1 by 15GiB
  • In the free disk space, create a 2GiB partition, format it with the xfs filesystem, and mount it persistently to the /var/log directory using the UUID.
    • The new mount point should include all the current contents of the /var/log directory. In other words, take care to avoid masking the current contents.
    • Ensure that the contents are relocated to the new mount point in such a way that the SELinux labels are preserved. Otherwise, services may not be able to write new logs.
  • Create another 2GiB partition and set it up to be used as swap space. Ensure it is also mounted persistently.
Solution
# first, increase size of vm disk
fdisk /dev/sda # add partition of type linux filesystem
mkfs.xfs /dev/partition
blkid | awk '/partition/ {print $2}' >> /etc/fstab
# complete mount spec in /etc/fstab
systemctl daemon-reload
# temporarily mount partition to move contents of /var/log
mount /dev/partition /mnt
mv /var/log/* /mnt
mount -a
umount /mnt
restorecon -R /var/log
fdisk /dev/sda # add partition of type linux swap
mkswap /dev/partition
swapon /dev/partition
blkid | awk '/partition/ {print $2}' >> /etc/fstab
# complete mount spec in /etc/fstab
systemctl daemon-reload
mount -a

Managing LVM Partitions

  1. On server1, create a logical volume with the name ldap that uses 500 8MiB extents.
  2. Format the ldap logical volume with the ext4 filesystem and ensure it mounts persistently on /home/ldap
  3. Increase the size of the rhel/root logical volume by 2GiB and the rhel/swap logical volume by 1GiB
Solution
  1. First, calculate the required partition size and then create a new partition. The partition size will need to be at least (number of extents + 1)*(extent size)
vgcreate vgroup -s 8M /dev/partition
lvcreate -l 250 -n ldap /dev/vgroup
mkfs.ext4 /dev/vgroup/ldap
blkid | awk '/ldap/ {print $2}' >> /etc/fstab
# complete mount spec in /etc/fstab
systemctl daemon-reload
mount -a
  1. First, calculate the required partition size and then create a new partition.
vgextend rhel /dev/partition
lvextend -r -L +2G /dev/rhel/root
lvextend -L +1G /dev/rhel/swap

Creating Users and Groups

On server2:

  1. Ensure that every new user on the system is required to reset their password every 45 days
  2. Ensure members of the group wheel have to reauthenticate after 240 minutes
  3. Ensure all other users with sudo privileges have to reauthenticate after 30 minutes
  4. Ensure members of the group management have the ability to change the password for all users on the system except root
  5. Create a group management with a GID of 2700
  6. Create a group engineering with a GID of 2800
  7. Create a group design with a GID of 2900
  8. Create a user with the following properties:
    • Name: boss
    • Password: “pass”
    • UID: 1700
    • Secondary groups: engineering, design, management
  9. Create a user with the following properties:
    • Name: bob
    • Password: “pass”
    • UID: 1800
    • Secondary groups: engineering
  10. Create a user with the following properties:
    • Name: sally
    • Password: “pass”
    • UID: 1801
    • Secondary groups: engineering
  11. Create a user with the following properties:
    • Name: joe
    • UID: 1900
    • Secondary groups: design
  12. Create a user mystery-app. Ensure this user cannot open an interactive shell and is using UID 567. Set this user’s home directory to /var/lib/mystery-app, but don’t create it.
  13. For each user except mystery-app, create a file in $HOME called ${USER}file
  14. For each user except mystery-app, create a file in $HOME called .myconfig
Solution
  1. Set PASS_MAX_DAYS 45 in /etc/login.defs
  2. Add Defaults:%wheel timestamp_timeout=240 to sudoers file or drop in
  3. Add Defaults timestamp_timeout=30 to sudoers file or drop in
  4. Add %management ALL = /bin/passwd, !/bin/passwd root
  5. groupadd --gid 2700 management
  6. groupadd --gid 2800 engineering
  7. groupadd --gid 2900 design
  8. useradd boss -G engineering,design --uid 1700
    passwd boss
  9. useradd bob -G engineering --uid 1800
    passwd bob
  10. useradd sally -G engineering --uid 1801
    passwd sally
  11. useradd joe -G design --uid 1900
    passwd joe
  12. useradd mystery-app --uid 567 -s /sbin/nologin
  13. for u in ${users[@]}; do su -l $u -c 'touch ~/${USER}file'; done
    for u in ${users[@]}; do su -l $u -c 'touch ~/.myconfig'; done

Managing Permissions

  1. On server2, create a shared group directory for the engineering team, /data/engineering, and ensure bob is the owner of that directory.
  2. Only the owner and the members of group engineering should have read and write permissions in this directory.
  3. Ensure that any new file that is created in this directory is group-owned automatically and can only be deleted by the owner or the user that created the file.
Solution
mkdir /data/engineering
chown bob:engineering /data/engineering
chmod 0770 /data/engineering
chmod g+s /data/engineering
chmod +t /data/engineering
# or
chmod 3770 /data/engineering

Managing Autofs

On server1:

  • Create the directories boss, bob, joe, and sally in the /home/ldap directory from a copy of /etc/skel.
  • Use NFS to share these directories with all hosts in server1’s 24-bit subnet mask.
  • Ensure that the directory ownerships are appropriate.

On server2:

  • Create a solution that automatically mounts server1:/home/ldap/$USER on /home/ldap/$USER on demand when these directories are accessed.
  • Copy all the contents from boss, bob, joe, and sally’s home directories into the appropriate /home/ldap subdirectory. Ensure that ownership is preserved.
  • Set the home directories for boss, bob, joe, and sally to the appropriate directories in /home/ldap. Remove their original default home directories.
  • Ensure that the directories are automatically mounted when the users login.
Solution
  • server1
    mkdir -p /home/ldap/boss
    chown 1700:1700 /home/ldap/boss
    mkdir -p /home/ldap/bob
    chown 1800:1800 /home/ldap/bob
    mkdir -p /home/ldap/sally
    chown 1801:1801 /home/ldap/sally
    mkdir -p /home/ldap/joe
    chown 1900:1900 /home/ldap/joe
    dnf install nfs-utils
    echo '/home/ldap 10.0.69.0/24(rw,no_root_squash)' >> /etc/exports
    firewall-cmd --permanent --add-service nfs
    firewall-cmd --permanent --add-service mountd
    firewall-cmd --permanent --add-service rpc-bind
    firewall-cmd --reload
    systemctl start nfs-server
  • server2
    dnf install nfs-utils autofs
    showmount -e server1
    # add '/home/ldap /etc/auto.ldap' to /etc/auto.master
    # add '* server1:/home/ldap/&' to /etc/auto.ldap
    systemctl enable --now autofs
    users=(boss bob sally joe)
    {
    for u in ${users[@]}; do
      cp -p /home/$u/.?* /home/ldap/$u
      usermod -d /home/ldap/$u
      chown -R $u:$u /home/ldap/$u
      rm -rf /home/$u
    done
    }

Scheduling Jobs

server2:

  1. Schedule a systemd timer job that writes “Good morning!” to syslog every Monday through Friday at 8AM. Make sure this job is executed as the user root with a 1 second accuracy.
  2. Schedule a systemd timer job that writes “Happy Monday!” to syslog every Monday at 8:01AM. Make sure this job is executed as the user boss with a 1 second accuracy. Ensure that this job will run even if the boss is out of the office.
  3. As the user bob:
    • Schedule a systemd timer job that creates the file /data/engineering/EOW-REMINDER with the contents “Have a great weekend, engineers! Don’t forget to submit your time sheets!” every Friday at 3:45PM. Make sure this job is executed as the user bob.
    • Schedule a systemd timer job that removes the /data/engineering/EOW-REMINDER file at 7:00PM every Friday.
  4. Schedule a cron job as user joe that creates a file every Friday at 3:30PM called $HOME/happy-friday containing the contents “submit your time sheet!”
Solution

Helpful start for any systemd timer

man systemd.time # view examples of time specification
systemctl list-timers # to get an example
systemctl cat logrotate.timer > ~/.config/systemd/user/mytimer.timer # start from example
  1. /etc/systemd/system/goodmorning.service

    [Unit]
    Description="Write Good morning! to syslog"
    
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/logger "Good morning!"

    /etc/systemd/system/goodmorning.timer

    [Unit]
    Description="Timer for goodmorning.service"
    
    [Timer]
    OnCalendar=Mon..Fri *-*-* 08:00:00
    AccuracySec=1
    Unit=goodmorning.service
    
    [Install]
    WantedBy=default.target
    systemctl enable --now goodmorning.timer
  2. loginctl enable-linger boss
    ssh boss@localhost # user units must be started by an actual login shell
    mkdir -p ~/.config/systemd/user

    ~/.config/systemd/user/happymonday.service

    [Unit]
    Description="Write Happy Monday! to syslog"
    
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/logger "Happy Monday!"

    ~/.config/systemd/user/happymonday.timer

    [Unit]
    Description="Timer for happymonday.service"
    
    [Timer]
    OnCalendar=Mon *-*-* 08:01:00
    AccuracySec=1
    Unit=happymonday.service
    
    [Install]
    WantedBy=default.target
    systemctl --user enable --now happymonday.timer
  3. loginctl enable-linger bob
    ssh bob@localhost # user units must be started by an actual login shell
    mkdir -p ~/.config/systemd/user

    ~/.config/systemd/user/eowreminder_send.service

    [Unit]
    Description="eow reminder"
    
    [Service]
    Type=oneshot
    ExecStart=/bin/bash -c 'echo "Have a great weekend, engineers! Don'\''t forget to submit your time sheets!" > /data/engineering/EOW-REMINDER'

    ~/.config/systemd/user/eowreminder_send.timer

    [Unit]
    Description="Timer for eowreminder-send.service"
    
    [Timer]
    OnCalendar=Fri *-*-* 15:45:00
    AccuracySec=1
    
    [Install]
    WantedBy=default.target

    ~/.config/systemd/user/eowreminder_clean.service

    [Unit]
    Description="eow reminder"
    
    [Service]
    Type=oneshot
    ExecStart=/bin/rm /data/engineering/EOW-REMINDER

    ~/.config/systemd/user/eowreminder_clean.timer

    [Unit]
    Description="Timer for eowreminder-clean.service"
    
    [Timer]
    OnCalendar=Fri *-*-* 19:00:00
    AccuracySec=1
    
    [Install]
    WantedBy=default.target
    systemctl --user enable --now eowreminder_send.timer
    systemctl --user enable --now eowreminder_clean.timer

Managing Flatpak

server2:

  • Add the rhel flatpak repository https://flatpaks.redhat.io/rhel.flatpakrepo. Ensure that this repository is accessible to everyone on the system.
  • Add the Flatpak repository https://dl.flathub.org/repo/flathub.flatpakrepo in such a way that it is accessible to user bob only.
  • As user bob, install the Scilab software package from the flathub repository in such a way that it is accessible to bob only
    • Hint: A symbolic link to a local directory will probably be needed. For example, /opt/flatpak/bob -> /home/ldap/bob/.local/share/flatpak
Solution
# Add system-wide repository
flatpak remote-add --system rhel https://flatpaks.redhat.io/rhel.flatpakrepo

# Add user-specific repository for bob
su -l bob
flatpak -u remote-add flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak -u install flathub org.scilab.Scilab

Setting Time

  • Configure server1 and server2 as an NTP client for pool.ntp.org
Solution
  • Replace current pool with pool pool.ntp.org iburst in /etc/chrony.conf
  • Restart chronyd

Managing SELinux

On server2:

  • Create the file /web/index.html with the contents “hello from server2”
  • Configure an Apache web server to listen on port 82 and serve content from /web.
  • Ensure that you can access the content both locally and from server1
Solution
# change port to 82 in /etc/httpd/conf/httpd.conf
# change document root to /web
semanage port list | grep http
semanage port -a -t http_port_t -p tcp 82
semanage fcontext -a -t http_sys_content_t '/web(/.*)?'
firewall-cmd --permanent --service http --get-ports
firewall-cmd --permanent --service http --add-port 82/tcp
firewall-cmd --reload
firewall-cmd --permanent --service http --get-ports
systemctl restart httpd; systemctl status httpd
curl localhost:82

Finding Files

On server1:

  1. Find all files in the /usr directory that have either the SUID or the SGID permission set and are smaller than 100KiB
    • Write each of these filenames (not the path) on separate lines to /root/sugidfiles.txt
  2. Find all files in the /usr directory that are executable by root:root only and not readable by any other user.
    • Copy these files to the directory /root/rootexec.
Solution
  1. find /usr -type f \( -perm -2000 -o -perm -4000 \) -size -100k -printf '%f\n' > /root/sugidfiles.txt
  2. mkdir /root/rootexec
    find /usr -type f -user root -group root -perm -0110 \! -perm -0001 \! -perm -0004 -exec cp {} /root/rootexec \;

Finding Patterns in Files

On server1:

  1. Find all lines that start with a ’#’ in the /etc directory and its subdirectories and following all symbolic links. Write the names of the files and matching lines to /root/comments.txt. Redirect standard error to /root/comments.err.
  2. Find all lines in /etc and its subdirectories that contain the words ‘Key’ or ‘keys’. Do not search any files with numerical, .bin, or .so extensions. Write the filename, line numbers, and matching lines to the file /root/keyfiles.txt. Redirect standard error to /root/keyfiles.err
Solution
  1. grep -R '^#' /etc > /root/comments.txt 2> /root/comments.err
  2. find /etc -type f \! \( -regex '.*\.[0-9]+$' -o -name '*.bin' -o -name '*.so' \) -exec grep -wnHE 'Key|keys' {} \; > /root/keyfiles.txt 2> /root/keyfiles.err

Grading the Exam

Grading scripts can be found here. Use whatever method you prefer to transfer the grading-scripts directory to server1 and run the evaluate script as root. My method for creating those grading scripts improved as I went along, but I didn’t have the time to go back and make all of them completely consistent. However, if you’ve done everything correctly, all the checks should pass. Feel free to submit a pull request if you have any improvement suggestions.

FYI: That repo also contains a qemu wrapper script called vm.sh. I encourage you to write your own, but it might be worth checking out if you’ve got a lot of studying left.

- Clint Jordan