At Red Hat Tech Day Netherlands 2026 in Bunnik, Fred van Zwieten and Ismail Dhaoui demoed the new Automation Portal in AAP 2.7, a self-service template gallery where operators click "Start" without knowing where a playbook lives or how it's configured. One of the templates in the demo catalog was "RHEL - Patch Servers (Maintenance Window)": apply the latest security patches to a host group during a scheduled window. Here's a runnable playbook that implements that same behavior.
---
- name: RHEL - Patch Servers (Maintenance Window)
hosts: rhel_servers
become: true
serial: 1
vars:
reboot_if_needed: true
patch_timeout: 3600
tasks:
- name: Verify current maintenance window is active
ansible.builtin.assert:
that:
- ansible_date_time.hour | int >= 1
- ansible_date_time.hour | int < 5
fail_msg: "Outside approved maintenance window (01:00-05:00). Aborting patch run."
success_msg: "Within maintenance window, proceeding with patching."
- name: Refresh dnf cache
ansible.builtin.dnf:
update_cache: true
- name: Apply all available security patches
ansible.builtin.dnf:
name: "*"
state: latest
security: true
register: patch_result
- name: Check if a reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_flag
- name: Reboot server if required and permitted
ansible.builtin.reboot:
reboot_timeout: "{{ patch_timeout }}"
msg: "Rebooting after security patch install (maintenance window)"
when:
- reboot_if_needed
- patch_result.changed
- reboot_flag.stat.exists
- name: Wait for host to respond after reboot
ansible.builtin.wait_for_connection:
delay: 10
timeout: 300
when:
- reboot_if_needed
- patch_result.changed
- reboot_flag.stat.exists
- name: Report patch outcome
ansible.builtin.debug:
msg: "Patching complete on {{ inventory_hostname }}. Changed: {{ patch_result.changed }}"
What it does
The play targets a rhel_servers group and runs serial: 1 so hosts are patched one at a time — a reasonable default for a maintenance window where you want to catch a failure before it spreads across the fleet. It first asserts the play is running inside the approved time range, then refreshes the dnf cache and installs security updates with ansible.builtin.dnf using security: true, which limits state: latest to packages with security advisories rather than a blanket full upgrade. If a reboot marker exists, it reboots with ansible.builtin.reboot and waits for the host to come back before moving on.
This maps directly to what a launched Automation Portal template does behind the scenes: the operator clicks "Start," AAP passes in the target group and any prompted survey variables (like reboot_if_needed), and the underlying job template runs a playbook like this one against the inventory tied to that template.
Notes / Gotchas
security: trueon thednfmodule requiresdnf-plugin-security(or equivalent) to be present on RHEL; without it the filter silently has no effect on some minimal images.- The window
assertabove is illustrative — in a real AAP job template this check is normally enforced by scheduling the job template itself to run only inside a window, not by comparingansible_date_timeinside the play. serial: 1is safe but slow for large fleets; increase it (e.g.serial: "10%") once you trust the patch set, but keep it low for the first host or two so a bad kernel update doesn't cascade.wait_for_connectionneedspatch_timeoutto be generous enough for the slowest host to reboot and re-establish SSH — 3600 seconds is conservative on purpose.
Takeaway
A maintenance-window patch playbook is really just dnf with a security filter, a conditional reboot, and a wait-for-reconnect — wrapping it as an Automation Portal template is what turns it into a one-click, self-service action for operators who don't need to touch the underlying YAML.
Related Articles
- Ansible Playbook Example: Emergency CVE Patch Rollout
- Ansible Playbook Example: Firewall Rule Request Approval Workflow
- Ansible Playbook Example: Remediate a Firewall Rule Misconfiguration