Automating RHEL Server Updates with Ansible

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 tasks
  • any_errors_fatal: true: Stops execution on all hosts if any host fails
  • serial: 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-latest comment suppresses ansible-lint warnings about using latest

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 required
  • 1: Reboot required
  • 2: 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 reboot module with timeout settings
  • reboot_timeout: 600: Waits up to 10 minutes for the reboot to complete
  • connect_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.