Module 5: Deploy, Configure, and Maintain Systems
RHCSA domain covered: Deploy, Configure, and Maintain Systems (6).
Labs in this kit: Lab 03 (services + timers), Lab 07 (cron, at, tuned), Lab 10 (bootloader).
This is the broadest domain — it covers scheduling work, managing services with systemd, installing software with dnf, tuning the system, and setting the boot target. The connecting thread is *making the system do the right thing automatically and persistently*: a job that runs every night, a service that comes back after reboot, a package that's actually installed and enabled. As everywhere on the RHCSA, "I ran it once" doesn't count — it has to be configured to survive.
Scheduling tasks: at, cron, and timers
What it is: three mechanisms for running work later. at runs a one-shot job at a future time; cron runs recurring jobs on a schedule; systemd *timers* are the modern equivalent of cron with better logging and dependency handling.
Why it matters: "run this script every day at X" and "schedule this to happen once in N minutes" are both common tasks, and choosing the right tool (and persisting it) is the skill.
# One-shot job in the future (at)
echo "/usr/local/bin/maintenance.sh" | at now + 5 minutes
atq # list pending at jobs
atrm <job-id> # cancel one
# Recurring system cron (preferred over /etc/crontab on RHEL 9)
cat > /etc/cron.d/rhcsa-job << 'EOF'
*/5 * * * * root /usr/local/sbin/check.sh >> /var/log/check.log 2>&1
EOF
# Per-user crontab
crontab -e -u bob # edit bob's crontab as root
crontab -l -u bob # list it
The cron time format is five fields: minute, hour, day-of-month, month, day-of-week. */5 * * * * means "every 5 minutes." The one gotcha that separates the two cron styles: files in /etc/cron.d/ and /etc/crontab need a user field (the root in the example above), because they're system-wide and cron needs to know who to run as. A *user* crontab edited with crontab -e has no user field — it implicitly runs as its owner. Mixing those up is a classic mistake. Use at only for "do this once, later"; use cron or a timer for anything recurring.
systemd service + timer pair
What it is: the modern, systemd-native way to schedule recurring work. A .service unit defines *what* to run; a .timer unit defines *when* to trigger it.
Why it matters: systemd timers are increasingly the expected answer over cron, and they have a feature cron lacks — catching up on missed runs.
# /etc/systemd/system/daily-maintenance.service
[Unit]
Description=KNZLABS Daily Maintenance
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/daily-maintenance.sh
# /etc/systemd/system/daily-maintenance.timer
[Unit]
Description=Run daily-maintenance every day at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload # make systemd read the new files
systemctl enable --now daily-maintenance.timer
systemctl list-timers # confirm it's scheduled
Two details earn points. You enable the timer, not the service — the timer is what triggers the service on schedule (Type=oneshot means the service runs to completion and exits, which is right for a maintenance script). And Persistent=true is the timer's superpower: if the machine was powered off at 03:30, a persistent timer runs the missed job at next boot, whereas cron would simply skip it. After writing or editing any unit file you must systemctl daemon-reload — systemd caches unit definitions and won't see your changes until you reload.
Package management with dnf
What it is: dnf is RHEL's package manager — it installs, removes, queries, and rolls back software, resolving dependencies automatically.
Why it matters: installing and managing packages is foundational, and the exam often runs against a *local* repo with no internet, so you need to know how to point dnf at it.
dnf install httpd # install (resolves dependencies)
dnf install -y ./local-pkg.rpm # from a local RPM file
dnf list installed # what's installed
dnf info httpd # details about a package
dnf history # transaction log
dnf history undo <id> # roll back a past transaction
# Install from a specific repo only (offline / air-gapped)
dnf install --disablerepo='*' --enablerepo=local httpd
The feature worth knowing for real work and the exam alike is dnf history — every transaction is logged with an ID, and dnf history undo <id> reverses it (uninstalling what was installed, reinstalling what was removed). That's your undo button when an install goes wrong. For air-gapped systems, --disablerepo='*' --enablerepo=local forces dnf to use only your local mirror and ignore any internet repos — exactly the rhcsa-repo setup in this lab.
Configuring a local repo from a directory of RPMs:
createrepo_c /var/www/html/repo/BaseOS # build the repodata index
cat > /etc/yum.repos.d/local.repo <<'EOF'
[local-baseos]
name=Local BaseOS
baseurl=http://rhcsa-repo/repo/BaseOS
enabled=1
gpgcheck=0
EOF
createrepo_c generates the metadata index dnf needs to treat a plain directory of RPMs as a repository. The .repo file then tells dnf where to find it. Setting gpgcheck=0 skips signature verification, which is fine for a trusted local mirror but something you'd think twice about in production.
tuned profiles
What it is: tuned is a daemon that applies a bundle of performance settings (CPU governor, disk scheduler, kernel parameters) appropriate to a workload, selected by *profile*.
Why it matters: "set the tuning profile to X" is a quick, common task, and tuned-adm is the one command you need.
dnf install -y tuned
systemctl enable --now tuned
tuned-adm list # available profiles
tuned-adm active # current profile
tuned-adm profile virtual-guest # apply a profile (persists)
tuned-adm recommend # what tuned suggests for this hardware
Common profiles: balanced (the general-purpose default), throughput-performance (servers that want raw throughput), virtual-guest (VMs, which is what the lab nodes are), powersave (laptops). tuned-adm profile <name> applies and persists in one step — no separate enable needed. If a task asks what profile is appropriate and you're unsure, tuned-adm recommend reads the hardware and tells you.
Boot target
What it is: the systemd target the system boots into by default (covered also in Module 2).
Why it matters: "configure the system to boot into multi-user mode" is a one-liner — as long as you use the persistent command.
systemctl get-default # current default
systemctl set-default multi-user.target
systemctl isolate rescue.target # switch NOW (does not persist)
set-default persists across reboots; isolate only changes the running state. For a "boot into" task, always set-default.
Common pitfalls
systemctl enable httpddoes NOT start the service now — it only arranges for it to start at boot. Useenable --nowto do both, or pairenablewithstart.- Forgetting
Persistent=trueon a timer → missed runs (machine was off) are never caught up. It's the whole reason to prefer timers over cron for important jobs. - Editing a unit file and forgetting
systemctl daemon-reload→ systemd keeps using the cached old version and your changes appear to do nothing. - Files in
/etc/cron.d/need a user field; user crontabs (crontab -e) don't. Putting a user field in a user crontab (or omitting it in/etc/cron.d/) breaks the job. - Enabling the
.serviceinstead of the.timer→ the job never fires on schedule. Enable the timer.
Exam tips
enable --nowis the safe default for "make this service run and survive reboot" — it covers both halves in one command.- After any unit-file work, the reflex sequence is
daemon-reload→enable --now <unit>→systemctl status <unit>to confirm it's actually running. systemctl list-timersis the fastest way to prove a timer task is scheduled correctly — it shows the next fire time.
Next: Module 6 — Basic networking.