Unpatched software is the leading cause of server compromise. The majority of publicly disclosed vulnerabilities already have patches available by the time attackers start actively exploiting them — the window between patch availability and active exploitation has shrunk from months to days. Manually patching servers is error-prone and inconsistent at scale. Automatic security updates ensure that critical CVEs are applied promptly without requiring manual intervention, while giving you control over what gets updated, when, and whether to reboot automatically. On RHEL 9, the dnf-automatic package provides a flexible, configurable automatic update framework with systemd timer integration, email notifications, and fine-grained control over which update types to apply.

This guide covers installing and configuring dnf-automatic on RHEL 9, limiting automatic updates to security fixes only (not feature updates), scheduling updates with systemd timers, configuring email notifications for update results, handling kernel updates and automatic reboots, and verifying the system is applying patches.

Prerequisites

  • RHEL 9 server with root or sudo access
  • Active RHEL subscription or configured EPEL/CentOS Stream repositories
  • Outbound HTTPS access to Red Hat CDN

Step 1 — Install dnf-automatic

dnf install -y dnf-automatic

Verify the installation:

rpm -q dnf-automatic

Step 2 — Understand the Configuration File

The configuration file is /etc/dnf/automatic.conf. It controls which packages are updated, whether to actually apply the updates or just download them, and how to send notifications.

cat /etc/dnf/automatic.conf

Key sections:

  • [commands] — what action to take: download only, download and install, or just check
  • [emitters] — how to report results: stdio, email, or motd
  • [email] — SMTP settings for email notifications
  • [base] — inherited dnf settings

Step 3 — Configure Security-Only Automatic Updates

vi /etc/dnf/automatic.conf
[commands]
# Options: default, security, security-severity:Critical, bugfix, enhancement, newpackage
upgrade_type = security

# Randomly wait up to this many seconds before starting (staggers updates across a fleet)
random_sleep = 3600

# Download packages (yes/no)
download_updates = yes

# Apply downloaded packages (yes/no)
apply_updates = yes

# Whether to reboot after updates if required
# Options: never, when-changed, when-needed
reboot = when-needed

# Message to show if a reboot is scheduled
reboot_command = "shutdown -r +5 'Automatic security update reboot in 5 minutes'"

[emitters]
# How to emit results: stdio, email, motd
emit_via = stdio,email

[email]
email_from = [email protected]
email_to = [email protected]
email_host = localhost
email_port = 25
email_tls = no

[base]
debuglevel = 1

Step 4 — Enable the systemd Timer

There are three systemd units provided by dnf-automatic:

  • dnf-automatic.timer — downloads and installs updates (applies your automatic.conf settings)
  • dnf-automatic-download.timer — downloads only
  • dnf-automatic-install.timer — downloads and installs (ignores apply_updates setting)

Enable the main timer:

systemctl enable --now dnf-automatic.timer

View the timer schedule:

systemctl list-timers dnf-*

Step 5 — Customise the Update Schedule

The default timer runs once per day at a random time. To apply updates during a specific maintenance window (e.g., 2:00 AM), create a systemd timer override:

mkdir -p /etc/systemd/system/dnf-automatic.timer.d
vi /etc/systemd/system/dnf-automatic.timer.d/schedule.conf
[Timer]
# Clear the default value
OnCalendar=
# Run every day at 2:00 AM server local time
OnCalendar=*-*-* 02:00:00
# Add random delay of up to 30 minutes to stagger fleet-wide updates
RandomizedDelaySec=1800
systemctl daemon-reload
systemctl restart dnf-automatic.timer

Step 6 — Exclude Specific Packages from Automatic Updates

Some packages (kernel, database engines, web servers) may need controlled updates due to configuration changes or compatibility requirements. Exclude them from automatic updates:

vi /etc/dnf/automatic.conf
[base]
# Packages to exclude from automatic updates (space-separated, wildcards accepted)
exclude = kernel* postgresql* nginx

Excluded packages can still be updated manually at any time:

dnf update kernel nginx --security

Step 7 — Handle Kernel Updates and Reboots

Kernel security updates require a reboot to take effect. Managing this in production requires a reboot strategy:

# Check if a reboot is needed after updates
needs-restarting -r
echo $?    # 0 = no reboot needed, 1 = reboot needed

# Check which services need to be restarted (not a full reboot)
needs-restarting -s

For automated reboots during maintenance windows, use the reboot = when-needed setting in automatic.conf. For fleet management, consider a rolling reboot strategy using Ansible:

# Check reboot status across all servers
ansible all -m command -a "needs-restarting -r" --become

Step 8 — Manual Testing and Dry Runs

# Check what security updates are available without applying them
dnf check-update --security

# List all available security advisories
dnf updateinfo list security

# Show details for a specific CVE
dnf updateinfo info CVE-2025-1234

# Run dnf-automatic once immediately (applies your config)
systemctl start dnf-automatic

# Check the result in journald
journalctl -u dnf-automatic -n 50

Step 9 — Configure MOTD Notifications

Instead of or in addition to email, update the message of the day so that when you SSH in you see a summary of recent automatic updates:

vi /etc/dnf/automatic.conf
[emitters]
emit_via = stdio,motd

The MOTD is written to /etc/motd.d/dnf-automatic and shown at login.

Verification Checklist

# Timer is active and scheduled
systemctl list-timers dnf-automatic.timer

# Check the last run
systemctl status dnf-automatic

# Journal log of last run
journalctl -u dnf-automatic --since today

# Pending security updates
dnf check-update --security

# Current update history
dnf history list

Troubleshooting

  • Updates not applying despite timer running — check apply_updates is yes in automatic.conf and that the correct timer unit (dnf-automatic.timer not dnf-automatic-download.timer) is enabled.
  • Timer shows as inactive — run systemctl enable --now dnf-automatic.timer and check for errors with systemctl status dnf-automatic.timer.
  • Email notifications not received — the default config uses localhost SMTP. If your server does not have a mail relay configured, either install postfix and configure it for relay, or switch emit_via = motd.

Security Considerations

  • Apply security updates automatically; apply feature updates manually. The upgrade_type = security setting implements this correctly.
  • Kernel updates are the highest-priority security patches. Schedule a regular reboot window and do not defer kernel updates indefinitely.
  • Monitor your update logs. If automatic updates are silently failing, you may be running vulnerable software without knowing it.

Conclusion

Your RHEL 9 server is now configured to apply security updates automatically during a defined maintenance window, with excluded packages for controlled services, email and MOTD notifications, and the ability to check reboot status for kernel updates. Automatic security patching dramatically reduces the time between vulnerability disclosure and protection.

Next steps: How to Create and Manage Swap Space on RHEL 9, How to Monitor System Resources with htop and vmstat on RHEL 9, and How to Perform a Security Audit with OpenSCAP on RHEL 9.