Free tech learning for everyone, from basics to pro

AnsibleProgramming and Application SecurityIntermediate

Ansible playbooks, tasks, handlers and conditionals

In short: A playbook is a YAML file of plays, each targeting a group of hosts with an ordered list of tasks. Conditionals, loops and registered results make tasks smart, and handlers run follow-up actions such as saving a config only when something changed.

Plays and tasks

A playbook is a YAML file containing one or more plays. Each play says which hosts to target and lists tasks to run in order. Each task calls one module with parameters and should have a clear name, because the name is what you see in the output and logs.

Inside a playbookPlaybooksite.ymlPlayhosts + settingsTasksmodule calls in orderHandlersrun on changeConditionals and loopswhen, loop
Figure 1. A playbook contains plays; each play targets hosts and runs tasks, which may notify handlers.
# ntp.yml
- name: Configure NTP on IOS switches
  hosts: switches
  gather_facts: false

  tasks:
    - name: Gather IOS facts
      cisco.ios.ios_facts:
        gather_subset: min

    - name: Show the software version
      ansible.builtin.debug:
        msg: "{{ inventory_hostname }} runs {{ ansible_net_version }}"

    - name: Ensure NTP servers are configured
      cisco.ios.ios_ntp_global:
        config:
          servers:
            - server: "{{ item }}"
              vrf: MGMT
        state: merged
      loop: "{{ ntp_servers }}"
      notify: Save config

  handlers:
    - name: Save config
      cisco.ios.ios_config:
        save_when: modified

Run it with ansible-playbook -i inventory.yml ntp.yml. Variables such as ntp_servers come from the inventory, and {{ ... }} is Jinja2 syntax for inserting a value.

Handlers

How a task runsEvaluate whenskip if falseRun moduleper hostResultok or changedNotifyif changedHandlerend of play
Figure 2. Handlers only run when a task reports changed, and they run once at the end of the play.

A handler is a task that only runs when another task notifies it and that task reported changed. Even if ten tasks notify the same handler, it runs once, at the end of the play. Classic uses are saving a switch configuration, restarting a service after its config file changes, or reloading a firewall policy.

Conditionals, loops and registered results

- name: Check interface status
  cisco.ios.ios_command:
    commands: show interfaces status | include notconnect
  register: notconnect

- name: Warn when many ports are unused
  ansible.builtin.debug:
    msg: "{{ inventory_hostname }} has {{ notconnect.stdout_lines[0] | length }} unused ports"
  when: notconnect.stdout_lines[0] | length > 20

- name: Install monitoring packages on Debian hosts only
  ansible.builtin.apt:
    name: [snmpd, chrony]
    state: present
  when: ansible_facts['os_family'] == "Debian"
  • register saves a task's result into a variable for later tasks.
  • when takes a condition written without {{ }}; the task is skipped if it is false.
  • loop repeats a task for each item in a list, available as item.

Running playbooks safely

Option or keywordUse
--check --diffPreview changes and show differences without applying them (where modules support it)
--limit acc-sw1Run against one host or a subset first
tags and --tags ntpRun only part of a large playbook
serial: 5Change hosts in batches rather than all at once
block / rescue / alwaysHandle errors and run clean-up steps
max_fail_percentageStop the run if too many hosts fail

Using serial together with max_fail_percentage gives you a rolling change: the first batch acts as a canary, and if it fails the playbook stops before touching the rest of the network. This mirrors how you would make a risky change by hand.

Tip: YAML is strict about indentation. Use two spaces, never tabs, and run ansible-playbook --syntax-check or ansible-lint before every run.

Practice

When does a handler run?
Only when a task that notifies it reports changed, and then once at the end of the play.
How do you run a playbook against a single switch first?
Use --limit with that host name, ideally together with --check --diff.
What does register do?
It stores the result of a task in a variable so later tasks can use it in conditions or messages.

Key takeaways

  • Playbooks contain plays; plays target hosts and run named tasks in order.
  • Handlers run once, at the end, only if notified by a changed task.
  • Use register, when and loop to make tasks react to data.
  • Preview with --check --diff and limit scope with --limit and serial.
  • Lint and syntax-check YAML before running.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.