Signal9Signal9 — home
online
all lessons

Deploy & Maintain: dnf Repositories, Packages, Time Sync, Boot Target

Beginner30 min6 steps
What you'll learn
  • Define a custom dnf repository as a .repo file and explain why gpgcheck protects you
  • Install, update, remove, search, and query packages with dnf — including a local .rpm file
  • Set the system timezone and clock with timedatectl and keep it synced with chrony
  • Choose the default boot target and rebuild the GRUB2 configuration safely
  1. Why a machine needs repositories

    Understand what a dnf repository is and why software comes from one trusted place.

    A Red Hat-family system installs software from repositories — network locations holding thousands of prebuilt .rpm packages plus an index that lists every package, its version, and what it depends on. The package manager dnf reads that index, works out the full dependency chain for whatever you asked for, downloads it, and installs it.

    Think of a repository as an app store with a printed catalogue. The catalogue (the repo metadata) tells dnf exactly what is on the shelves and what each item needs to function, so dnf never has to guess. Without a configured repo, dnf install httpd has nowhere to look — it's a phone with no app store account.

    You tell dnf where the shelves are by dropping a small text file into `/etc/yum.repos.d/`. Any file there ending in .repo is read at the start of every dnf run.

  2. Define a .repo file by hand

    Write a working repository definition and understand each required line.

    A repo definition is plain INI-style text. The exam task is almost always "a repo is available at http://<host>/..., configure the system to use it." Here is the minimum that does the job, written to /etc/yum.repos.d/internal.repo:

    Example — not runnable on your lab box

    cat /etc/yum.repos.d/internal.repo
    [internal]
    name=Internal Tools Repo
    baseurl=http://mirror.example.test/repo/internal/
    enabled=1
    gpgcheck=1
    gpgkey=http://mirror.example.test/repo/RPM-GPG-KEY-internal
    Six lines, each load-bearing. `[internal]` is the repo id (the bracketed label, must be unique on the box). `name=` is a human-readable description. `baseurl=` is where the packages and metadata live. `enabled=1` means dnf actually uses it. `gpgcheck=1` demands a valid signature on every package, and `gpgkey=` says which public key verifies that signature.

    Example — not runnable on your lab box

    dnf repolist
    repo id            repo name
    baseos             Rocky Linux 9 - BaseOS
    appstream          Rocky Linux 9 - AppStream
    internal           Internal Tools Repo
    repolist
    List enabled repositories. Add --all to include disabled ones too.
    repolist shows every enabled repo dnf currently sees. Your new internal id appearing here is the proof the file was parsed and is live. If it's missing, dnf never read your definition — check the filename ends in .repo and enabled=1.

    Check yourself

    Your repo file has gpgcheck=1 but no gpgkey line, and installs fail with a GPG error. What is the correct fix?

  3. Manage packages with dnf

    Install, update, remove, search, and query packages — including a local rpm.

    Once a repo is wired up, the day-to-day verbs are few and predictable. Each one resolves dependencies for you, so you rarely chase a missing library by hand.

    Example — not runnable on your lab box

    sudo dnf install httpd
    Dependencies resolved.
    ================================================================
     Package        Arch     Version          Repository      Size
    ================================================================
    Installing:
     httpd          x86_64   2.4.57-5.el9     appstream       46 k
    Installing dependencies:
     httpd-core     x86_64   2.4.57-5.el9     appstream      1.5 M
    ...
    Is this ok [y/N]:
    install <pkg>
    Install a package and its dependencies from enabled repos.
    -y
    Assume yes — skip the prompt. Useful in scripts, dangerous interactively.
    install pulls the named package plus every dependency, shows the plan, and waits for confirmation. The Repository column tells you which repo each piece comes from — handy for confirming your custom repo is actually being used.

    Example — not runnable on your lab box

    sudo dnf install ./monitoring-agent-2.1-1.el9.x86_64.rpm
    Dependencies resolved.
    Installing:
     monitoring-agent   x86_64   2.1-1.el9   @commandline   220 k
    Is this ok [y/N]:
    install ./file.rpm
    Install a local rpm file; dnf still pulls its dependencies from repos.
    When you have an rpm file sitting on disk, give dnf the path (the ./ or a full path is what marks it as a local file, not a repo name). dnf still resolves the file's dependencies from your enabled repos — a big advantage over the old rpm -i, which would just error out on a missing library.

    The rest of the verbs round out the lifecycle. update (alias upgrade) brings packages to their newest version; with no name it updates everything. remove (alias erase) uninstalls and removes now-unneeded dependencies. list and provides answer questions before you change anything.

    Example — not runnable on your lab box

    dnf provides /usr/sbin/httpd
    httpd-core-2.4.57-5.el9.x86_64 : Apache HTTP Server (core)
    Repo        : appstream
    Matched from:
    Filename    : /usr/sbin/httpd
    provides <path|cmd>
    Find which package provides a given file or capability.
    list <pattern>
    List available/installed packages matching a name pattern.
    provides answers the reverse question: which package owns this file or command? Indispensable when an exam task says "the foo command must be available" but doesn't tell you the package name. Here it reveals that /usr/sbin/httpd is shipped by httpd-core.

    Check yourself

    A task says the semanage command must be present, but doesn't name the package. Which dnf subcommand finds the owning package?

  4. Set the time, then keep it synced

    Use timedatectl for timezone/clock and chrony to keep the clock accurate.

    Correct time is not cosmetic. TLS certificates, Kerberos tickets, log correlation, and scheduled jobs all break when a clock drifts. There are two separate jobs here, and mixing them up is the trap:

    • `timedatectl` sets the timezone and can set the clock — the one-time configuration.
    • `chrony` (its daemon is chronyd) continuously corrects the clock against network time servers — the ongoing maintenance.

    Think of it like setting a wall clock: timedatectl is you turning the hands to the right time once; chrony is a tiny technician living inside who nudges the hands a fraction every minute so it never drifts.

    timedatectl
                   Local time: Mon 2026-06-23 14:05:11 UTC
               Universal time: Mon 2026-06-23 14:05:11 UTC
                     RTC time: Mon 2026-06-23 14:05:11
                    Time zone: Etc/UTC (UTC, +0000)
    System clock synchronized: yes
                  NTP service: active
    Run with no arguments it's a status report. The two lines that matter for exam grading: `Time zone:` (is it the one the task asked for?) and `NTP service: active` plus `System clock synchronized: yes` (is chrony running and has it locked on?).
    sudo timedatectl set-timezone America/Chicago
    set-timezone <zone>
    Set the system timezone (updates /etc/localtime).
    set-time '<YYYY-MM-DD HH:MM:SS>'
    Manually set the clock — refused while NTP sync is on (see mistake below).
    list-timezones
    Print every valid timezone name.
    Silent success, the systemd convention. This updates the /etc/localtime symlink for you — far safer than editing that link by hand. Use timedatectl list-timezones | grep -i chicago when you can't recall the exact spelling.

    chrony is configured in /etc/chrony.conf (a server or pool line names the upstream time sources). Once chronyd is running and enabled, you confirm it's actually talking to those servers with chronyc sources.

    Example — not runnable on your lab box

    chronyc sources
    MS Name/IP address         Stratum Poll Reach LastRx Last sample
    ===============================================================================
    ^* ntp1.example.test             2   6   377    21   +14us[  +20us] +/-  9ms
    ^- 192.0.2.123                   3   6   377    23  -2117us[-2089us] +/-  41ms
    sources
    Show the time servers chrony knows and which one it's synced to.
    tracking
    Show how far off and how fast the local clock is being corrected.
    Each line is one time source. The leading marks tell the story: *`^** is the source chrony has **selected and synced to**, ^- is an acceptable but unused candidate. A healthy **Reach** of 377 (octal — all 8 recent polls answered) means the link is solid. If every source showed ? or Reach 0`, chrony can't reach its servers and the clock won't stay correct.

    Check yourself

    In chronyc sources output, which leading symbol marks the server the system is currently synchronized to?

  5. Choose the boot target and rebuild GRUB

    Set the default systemd target and regenerate the bootloader config correctly.

    Two different things decide how the machine comes up, and the exam tests both:

    • The default target — does it boot to a full graphical desktop (graphical.target) or to a text-only multi-user system (multi-user.target)? This is systemd's job, set with systemctl set-default.
    • The GRUB2 configuration — the menu and kernel options the bootloader uses before systemd even starts. After changing kernel options (in /etc/default/grub) you must regenerate the compiled config file.
    sudo systemctl set-default multi-user.target
    Removed /etc/systemd/system/default.target.
    Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target.
    set-default <target>
    Set the target the system boots into (multi-user vs graphical).
    get-default
    Print the current default boot target.
    set-default just repoints the default.target symlink. Booting to multi-user.target (text, no GUI) is the standard server choice and what most RHCSA tasks ask for. Check the current setting any time with systemctl get-default.

    Changing the boot target above takes effect on the next boot with no further steps. But editing kernel parameters is different: those live in /etc/default/grub, and that file is only a source. GRUB actually reads a compiled file you must regenerate.

    Example — not runnable on your lab box

    sudo grub2-mkconfig -o /boot/grub2/grub.cfg
    Generating grub configuration file ...
    Found linux image: /boot/vmlinuz-5.14.0-362.el9.x86_64
    Found initrd image: /boot/initramfs-5.14.0-362.el9.x86_64.img
    done
    -o <file>
    Output path for the generated config — must be the real grub.cfg GRUB loads.
    grub2-mkconfig reads /etc/default/grub and the scripts in /etc/grub.d/, then writes the result to the file named by -o. On a BIOS system that's `/boot/grub2/grub.cfg`; on UEFI it's /boot/efi/EFI/<distro>/grub.cfg. Until you run this, your /etc/default/grub edits are inert.

    Check yourself

    You added a kernel parameter in /etc/default/grub, rebooted, and the parameter isn't active. What step did you skip?

  6. Try it in your live terminal

    Run safe, read-only checks on the real sandbox to your right.

    You have a real root shell. These commands are read-only — they inspect state without installing, removing, or rebooting anything. Run them in order and read each result against what you learned above. This is exactly the 'verify before and after' habit a graded deploy-and-maintain task rewards.

    Example — not runnable on your lab box

    dnf repolist
    List the enabled repositories. Confirm the box knows where to get software — and that any repo you'd expect (like a custom one) is present and enabled.
    timedatectl
    Check the timezone, whether NTP is active, and whether the clock is synchronized. These are the exact fields a time-config task grades.
    chronyc sources 2>/dev/null || true
    Show chrony's time sources and which one it's locked onto (look for ^*). The || true keeps the step clean if chrony isn't installed on this particular sandbox.
    systemctl get-default
    Print the current default boot target — multi-user.target (text) or graphical.target (GUI). No change is made; you're just confirming the system's boot intent.
Recap
  • A repo is configured as a .repo file in /etc/yum.repos.d/ with an [id], baseurl=, enabled=1, and signature settings — dnf repolist proves it loaded.
  • Keep gpgcheck=1 and supply the matching gpgkey=; the fix for a GPG failure is the right key, never disabling the check.
  • dnf resolves dependencies for install/update/remove and for a local ./file.rpm; provides maps a file or command back to its package.
  • Two separate time jobs: timedatectl sets timezone/clock once; chrony/chronyd keeps it synced — don't set the clock by hand while sync is on.
  • systemctl set-default picks the boot target; kernel-option changes need grub2-mkconfig -o <real grub.cfg> or they never take effect.
Command → what it does
CommandEffectWatch for
/etc/yum.repos.d/x.repoDefines a repo (id, baseurl, enabled, gpg)File must end in .repo; enabled=1
dnf install <pkg> / ./x.rpmInstall package or local rpm + depsLocal file needs a path (./)
dnf provides <path>Find the package owning a file/commandUse when the task hides the package name
timedatectl set-timezone <z>Set the system timezoneSpelling — use list-timezones
chronyc sourcesShow time sources + synced one^* = synced; Reach 377 = healthy
systemctl set-default <t>Set the default boot targetmulti-user (text) vs graphical
grub2-mkconfig -o <grub.cfg>Rebuild compiled GRUB configBIOS vs UEFI output path