Getting Started with Ansible for Patch Management

Eran Goldman-Malka · November 3, 2025

Patch management is where good intentions collide with operational reality. Change windows, fragile shell loops, manual spreadsheet tracking—this is the state of most infrastructure teams before they standardise. Ansible changes the contract: one control node, agentless architecture, human-readable playbooks, and a single command to validate connectivity across every managed host before you touch a package.

Why Agentless Matters

Most automation tools require an agent running on every managed node—meaning agent version drift, additional port exposure, and another process to patch. Ansible uses SSH (or WinRM for Windows), which you already have open and already manage. There is nothing to install on targets, only on the control node.


Control Node vs Managed Nodes

Role Description
Control node Where Ansible runs. Requires Python 3.x. Linux or macOS native; Windows requires WSL2.
Managed node Any host Ansible connects to. Requires Python 3.x and SSH access. No daemon.

Ansible never daemonizes on managed nodes. Every run is a push from the control node outward.


Installing Ansible

Ubuntu / Debian:

sudo apt update && sudo apt install -y ansible

RHEL / CentOS / Fedora:

sudo dnf install -y ansible

macOS (Homebrew):

brew install ansible

Windows: Ansible control is not natively supported on Windows. Use WSL2:

wsl --install
# Then follow the Ubuntu steps above inside the WSL2 shell

Verify the installation:

ansible --version

SSH Key-Based Authentication

Ansible needs passwordless SSH access to managed nodes. Generate a dedicated key pair on the control node:

ssh-keygen -t ed25519 -C "ansible-control" -f ~/.ssh/ansible_id

Copy the public key to each managed node:

ssh-copy-id -i ~/.ssh/ansible_id.pub adminuser@TARGET_HOST

Test the connection manually before running any playbook:

ssh -i ~/.ssh/ansible_id adminuser@TARGET_HOST

Inventory Basics

The inventory tells Ansible which hosts exist and how to reach them.

INI format (inventory.ini):

[webservers]
web01.example.com
web02.example.com

[dbservers]
db01.example.com

[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/ansible_id

YAML format (inventory.yml):

all:
  children:
    webservers:
      hosts:
        web01.example.com:
        web02.example.com:
    dbservers:
      hosts:
        db01.example.com:
  vars:
    ansible_user: ubuntu
    ansible_ssh_private_key_file: ~/.ssh/ansible_id

Validate Connectivity Before You Patch

Never start a patching run without confirming every host responds. The ping module is not ICMP—it tests the full Ansible connection stack (SSH + Python):

ansible all -i inventory.ini -m ping

Expected output for a healthy host:

web01.example.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Any UNREACHABLE or FAILED here becomes a failed patch run. Fix connectivity first.


First Playbook: Dry-Run Package Update

Create update-check.yml:

---
- name: Dry-run package update on all hosts
  hosts: all
  become: true
  tasks:
    - name: Simulate package upgrade (apt)
      ansible.builtin.apt:
        upgrade: dist
        update_cache: true
      check_mode: true
      register: apt_result

    - name: Show what would change
      ansible.builtin.debug:
        var: apt_result

Run it with check mode and diff:

ansible-playbook -i inventory.ini update-check.yml --check --diff

--check simulates execution without making changes. --diff shows exactly which packages or files would be modified. Nothing touches the managed nodes until you remove --check.


Next in the series: Must-Know Ansible Commands and Core Concepts — ad-hoc commands, targeting patterns, privilege escalation, and running security-only updates from the command line.

Twitter, Facebook