Why Ansible Playbooks Fail

Ansible is the industry-standard tool for infrastructure automation and configuration management. It’s agentless (uses SSH), uses simple YAML syntax, and is incredibly powerful—but playbook failures can be cryptic and frustrating, especially when managing dozens or hundreds of servers.

This guide covers the most common failure scenarios and how to systematically debug them.

Prerequisites

  • Ansible 2.14+ installed on your control node.
  • SSH key-based authentication configured to target hosts.
  • A working inventory file (/etc/ansible/hosts or a custom .ini/.yml file).

Common Errors and Solutions

1. SSH Connection Failures

Error: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh"}

This is the most fundamental error. Ansible communicates entirely over SSH.

Debugging Steps:

  1. Test manually: ssh -i ~/.ssh/your_key user@target_ip
  2. Check inventory: Ensure ansible_host, ansible_user, and ansible_ssh_private_key_file are correct.
  3. Firewall: Verify port 22 is open on the target (sudo ufw status or iptables -L).
  4. Bastion/Jump Host: If the target is behind a bastion, configure ProxyJump:
    [webservers]
    internal-web ansible_host=10.0.0.5 ansible_ssh_common_args='-o ProxyJump=bastion_user@bastion_ip'

2. Privilege Escalation (become) Failures

Error: fatal: [host]: FAILED! => {"msg": "Missing sudo password"}

Ansible needs become: yes to run tasks as root, but the remote user may require a sudo password.

Solutions:

  • Option A: Configure passwordless sudo for the Ansible user on the remote host:
    # On the remote host, add to /etc/sudoers (via visudo):
    ansible_user ALL=(ALL) NOPASSWD:ALL
  • Option B: Pass the sudo password at runtime:
    ansible-playbook site.yml --ask-become-pass
  • Option C: Store it encrypted in Ansible Vault:
    ansible-vault encrypt_string 'MyS3cretPa$$' --name 'ansible_become_password'

3. Undefined Variable Errors

Error: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'my_var' is undefined"}

Variables in Ansible have a complex precedence hierarchy (inventory, group_vars, host_vars, role defaults, play vars, extra vars, etc.).

Debugging Steps:

  1. Print all vars for a host:
    ansible target_host -m debug -a "var=hostvars[inventory_hostname]"
  2. Add a debug task before the failure:
    - name: Debug my_var
      debug:
        var: my_var
  3. Check variable scope: If the variable is defined in a role’s defaults/main.yml, it can be overridden by almost anything. If it’s in vars/main.yml, it has higher precedence.
  4. Provide a default: Use {{ my_var | default('fallback_value') }} to prevent failures on optional variables.

4. Idempotency Issues (Task Fails on Re-run)

Problem: The playbook works the first time but fails on the second run because it tries to create something that already exists.

Root Cause: Using command or shell modules instead of purpose-built modules.

Bad (Not Idempotent):

- name: Create app directory
  command: mkdir /opt/myapp

Good (Idempotent):

- name: Create app directory
  file:
    path: /opt/myapp
    state: directory
    owner: appuser
    mode: '0755'

If you must use command/shell, add conditions:

- name: Initialize database (only if not already done)
  command: /opt/myapp/init_db.sh
  args:
    creates: /opt/myapp/.db_initialized

5. Module Not Found / Collection Missing

Error: ERROR! couldn't resolve module/action 'community.general.ufw'

In Ansible 2.10+, many modules were moved into separate Collections.

Fix:

ansible-galaxy collection install community.general

Add a requirements.yml to your project root so others can install dependencies:

# requirements.yml
collections:
  - name: community.general
  - name: ansible.posix

Install with: ansible-galaxy install -r requirements.yml


Summary

ErrorFirst Thing to Check
SSH Connection FailedTest ssh manually to the target
Missing sudo passwordConfigure passwordless sudo or --ask-become-pass
Undefined variableAdd debug: var=my_var task; check precedence
Task fails on re-runReplace command/shell with built-in modules
Module not foundInstall the required Collection via ansible-galaxy