Automating RHEL Server Updates with Ansible #
Introduction #
I hate updating my servers manually so I’ve set up this playbook to run updates. This was probably the first playbook I ever wrote for my home lab, and it’s been running automatically for years now on a weekly schedule every Friday night through AAP (Ansible Automation Platform).
This guide shows you how to automate RHEL (and other yum/dnf based distros like Fedora, CentOS etc.) server updates using Ansible, including proper reboot handling.
The playbook does the following:
- Updates all packages to their latest versions
- Checks if a reboot is required after updates
- Automatically reboots servers when necessary
- Waits for servers to come back online
The Playbook #
Here’s the Ansible playbook that automates the entire update process:
---
- name: Updates
hosts: all
become: true
gather_facts: true
any_errors_fatal: true
serial: 1
vars_files:
- "../group_vars/all/vault.yml"
tasks:
- name: Ensure dnf-utils is installed
ansible.builtin.dnf:
name: dnf-utils
state: present
- name: Ensure latest packages are installed
ansible.builtin.dnf:
name: "*"
state: latest
update_cache: true
# noqa: package-latest
- name: Check if reboot is required
ansible.builtin.command:
cmd: "needs-restarting -r -s"
register: needs_restarting
changed_when: needs_restarting.rc == 1
failed_when: needs_restarting.rc == 2
- name: Reboot the server if needed
ansible.builtin.reboot:
reboot_timeout: 600
connect_timeout: 10
when: needs_restarting.rc == 1
Step-by-step #
Below is a full breakdown of every single task and each of the settings used:
Playbook Settings #
- name: Updates
hosts: all
become: true
gather_facts: true
any_errors_fatal: true
serial: 1
vars_files:
- "../group_vars/all/vault.yml"
become: true: Runs tasks with root privileges (required for package updates)gather_facts: true: Collects system information before running tasksany_errors_fatal: true: Stops execution on all hosts if any host failsserial: 1: Updates servers one at a time instead of all simultaneously (safer for production)vars_files: Includes encrypted variables from Ansible Vault
Prerequisites #
- name: Ensure dnf-utils is installed
ansible.builtin.dnf:
name: dnf-utils
state: present
This task ensures that dnf-utils is installed, which provides the needs-restarting command used later for reboot detection.
Package Updates #
- name: Ensure latest packages are installed
ansible.builtin.dnf:
name: "*"
state: latest
update_cache: true
# noqa: package-latest
This task uses the dnf module to:
- Update all packages (
name: "*") - Ensure latest versions (
state: latest) - Refresh package cache (
update_cache: true) - The
# noqa: package-latestcomment suppresses ansible-lint warnings about usinglatest
Reboot Detection #
- name: Check if reboot is required
ansible.builtin.command:
cmd: "needs-restarting -r -s"
register: needs_restarting
changed_when: needs_restarting.rc == 1
failed_when: needs_restarting.rc == 2
The needs-restarting command is part of the dnf-utils package and:
-r: Checks if a reboot is required-s: Silent mode (only exit codes matter)
Exit codes:
0: No reboot required1: Reboot required2: Error occurred
Conditional Reboot #
- name: Reboot the server if needed
ansible.builtin.reboot:
reboot_timeout: 600
connect_timeout: 10
when: needs_restarting.rc == 1
This task:
- Only runs when
needs_restarting.rc == 1(reboot required) - Uses the built-in
rebootmodule with timeout settings reboot_timeout: 600: Waits up to 10 minutes for the reboot to completeconnect_timeout: 10: Waits 10 seconds before attempting to reconnect
My Experience #
The beauty of this setup is that I get zero downtime. My most critical services like DNS (BIND9) and Red Hat IdM (IPA) run on multiple servers, and because of the serial: 1 setting, the servers reboot one at a time. This means that while one DNS server is rebooting, the other one continues serving requests, so everything just works seamlessly.
Automatic updates (at least for me) is one of those “set it and forget it” automations that just makes life easier.