Ansible Patch Management
Software updates fix security holes and bugs. Unpatched servers invite trouble, yet patching a large fleet by hand is slow and error prone. Ansible turns patching into a repeatable routine: check what needs updates, apply them in safe batches, reboot when needed, and verify that services return. Consider a school bus fleet. Each bus needs regular maintenance, and the school never takes every bus off the road on the same morning.
Diagram: The Patch Cycle
┌──────────────┐
│ 1. Snapshot │ backup or VM snapshot
└──────┬───────┘
▼
┌──────────────┐
│ 2. Drain │ remove from load balancer
└──────┬───────┘
▼
┌──────────────┐
│ 3. Update │ apply packages
└──────┬───────┘
▼
┌──────────────┐
│ 4. Reboot │ only if required
└──────┬───────┘
▼
┌──────────────┐
│ 5. Verify │ health check
└──────┬───────┘
▼
┌──────────────┐
│ 6. Return │ add back to load balancer
└──────────────┘
Update Packages on Debian Systems
- name: Patch Debian servers
hosts: debian_servers
become: true
serial: 2
tasks:
- name: Refresh the package list
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Apply all upgrades
ansible.builtin.apt:
upgrade: dist
autoremove: true
register: apt_result
Update Packages on Red Hat Systems
- name: Patch Red Hat servers
hosts: redhat_servers
become: true
serial: 2
tasks:
- name: Install every available update
ansible.builtin.dnf:
name: "*"
state: latest
register: dnf_result
Restrict a run to security fixes by adding security: true to the dnf task.
Decide When to Reboot
Kernel and core library updates need a reboot. Debian systems create the file /var/run/reboot-required. Red Hat systems provide the needs-restarting command.
- name: Check for a reboot flag on Debian
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_flag
- name: Reboot the server when required
ansible.builtin.reboot:
reboot_timeout: 600
msg: "Reboot started by Ansible patching"
when: reboot_flag.stat.exists
The reboot module waits until the server answers again before the play continues.
Combine Both Families in One Playbook
- name: Update Debian family
ansible.builtin.apt:
upgrade: dist
when: ansible_os_family == "Debian"
- name: Update Red Hat family
ansible.builtin.dnf:
name: "*"
state: latest
when: ansible_os_family == "RedHat"
Protect Against Problems
- Run
serialso only a small batch changes at a time - Set
max_fail_percentage: 0to stop the rollout after a failure - Hold risky packages with the
excludeoption or a package hold - Patch staging first and production after a few days of observation
- Schedule changes inside an approved maintenance window
- name: Update everything except the database engine
ansible.builtin.dnf:
name: "*"
state: latest
exclude: "mariadb*"
Verify After Patching
post_tasks:
- name: Confirm the web service responds
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}/health"
status_code: 200
register: check
retries: 6
delay: 10
until: check.status == 200
Create a Patch Report
- name: Record the last patch date
ansible.builtin.copy:
content: "Patched on {{ ansible_date_time.iso8601 }}\n"
dest: /etc/last_patch_date
Collect these files or send results to a monitoring system. Auditors and managers value a clear record of patch dates for each server.
Schedule the Job
Run patch playbooks from cron, a CI pipeline, or AWX schedules. AWX offers calendars, notifications, and logs for every run, which suits monthly patch windows.
Try This
Create a playbook with serial: 1 that refreshes the package cache, lists upgradable packages with apt list --upgradable or dnf check-update, and prints the result. Run it in check mode against two lab machines before applying a real update.
Summary
Ansible patching follows a cycle of snapshot, drain, update, reboot, verify, and return. Package modules handle Debian and Red Hat families, and the reboot module restarts servers safely. Batches through serial, failure limits, health checks, and reports keep patching predictable. Scheduling through AWX or a pipeline turns the routine into a dependable monthly habit.
