Ansible errors are easier to fix when you troubleshoot them in layers. Do not start by editing the failed task immediately. First confirm that Ansible can parse the inventory, target the expected hosts, connect over SSH, become the required user, parse the playbook, load variables and collections, decrypt Vault data, render templates, and then run the task logic.
When a playbook fails, check these layers in order:
- Can Ansible parse the inventory?
- Does the playbook target the expected hosts?
- Can Ansible connect over SSH?
- Can the remote user use sudo or become when required?
- Is the playbook valid YAML?
- Are variables, facts, collections, Vault files, and templates loading correctly?
- Is the failed task itself written correctly?
Tested on: Rocky Linux 10.2 (Red Quartz); kernel 6.12.0-211.16.1.el10_2.0.1.x86_64; ansible-core 2.16.16.
~/ansible-project, inventory group lab, and playbooks in playbooks/. Use your own host names and paths if yours differ.
| Need | Use this page for | Dedicated guide |
|---|---|---|
| Playbook failed and you do not know why | Identify the failing layer | This troubleshooting hub |
How to Troubleshoot Ansible Errors
Work from the outside in: connectivity and parsing problems look like task failures but are not fixed by changing module arguments.
| Play recap status | Meaning |
|---|---|
ok |
Task ran; desired state already met |
changed |
Task ran and modified the host |
skipped |
when: or other condition prevented execution |
unreachable |
Ansible could not connect or run Python on the host |
failed |
Task ran but reported an error |
When output is not obvious, rerun with more verbosity (-v through -vvvv) or target one host with -l rocky2 so logs stay readable. For playbook CLI flags and check mode, see how to run Ansible playbooks. Quick module probes use ad hoc commands.
Quick Ansible Troubleshooting Checklist
Run these commands before you edit playbook tasks. Each row is a quick yes/no check for one layer—inventory, connectivity, syntax, or collections. If a command fails or looks wrong, skip to the matching section below (Inventory, SSH, YAML, and so on). This table tells you where to look; the sections under Common Ansible Errors and Quick Fixes show what to change.
If your project ansible.cfg already sets inventory = inventory/hosts, you can omit -i inventory/hosts on commands below.
| Check | Command | What it confirms |
|---|---|---|
| Ansible runtime | ansible --version |
Config file, Python version, module path, collection paths |
| Inventory parses | ansible-inventory -i inventory/hosts --list |
Inventory syntax and host vars load |
| Groups are visible | ansible-inventory -i inventory/hosts --graph |
Groups and hosts are wired correctly |
| Play targets hosts | ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --list-hosts |
hosts: pattern matches expected hosts |
| SSH works | ansible -i inventory/hosts lab -m ansible.builtin.ping |
Ansible can connect and run Python |
| SSH debug | ansible -i inventory/hosts rocky2 -m ansible.builtin.ping -vvvv |
Shows SSH user, key, port, and remote command |
| Syntax is valid | ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --syntax-check |
YAML and playbook structure parse |
| Tasks are visible | ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --list-tasks |
Task order and imports are visible |
| Dry run | ansible-playbook -i inventory/hosts playbooks/file-demo.yml --check --diff |
Shows planned changes where supported |
| Config check | ansible-config dump --only-changed |
Active ansible.cfg overrides |
| Collections | ansible-galaxy collection list |
Installed collections and versions |
ansible --version is the first command when a playbook works on your laptop but fails in CI—it shows which config file, Python, and collection paths that shell is using:
ansible --versionSample output:
ansible [core 2.16.16]
config file = /home/ansible/ansible-project/ansible.cfg
configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.12/site-packages/ansible
ansible collection location = /home/ansible/.ansible/collections:/usr/share/ansible/collectionsIf config file points at the wrong path, Ansible may be ignoring your project ansible.cfg.
To confirm groups and hosts before running a play, list the inventory tree from the project directory:
cd ~/ansible-projectansible-inventory -i inventory/hosts --graphSample output:
@all:
|--@ungrouped:
|--@lab:
| |--rocky2The lab group contains rocky2—if your play uses hosts: lab, that hostname should also appear when you run --list-hosts.
Build a Minimal Reproducer
Before you edit a large playbook, shrink the failure to the smallest command sequence that still fails. Work through these steps in order—stop when one step breaks and fix that layer before returning to the full playbook.
| Step | Command | What it isolates |
|---|---|---|
| 1. Inventory + SSH | ansible -i inventory/hosts rocky2 -m ansible.builtin.ping |
Host pattern, connectivity, Python on the target |
| 2. Module FQCN | ansible-doc -t module ansible.builtin.copy |
Correct module name and documented options |
| 3. Playbook parse | ansible-playbook -i inventory/hosts playbooks/site.yml --syntax-check |
YAML structure and task keys |
| 4. Dry run | ansible-playbook -i inventory/hosts playbooks/site.yml --check |
Planned changes without applying them (where modules support check mode) |
| 5. SSH detail | ansible -i inventory/hosts rocky2 -m ansible.builtin.ping -vvv |
SSH user, key, port, and remote command Ansible actually uses |
Step 1 confirms Ansible can reach the host at all. Step 2 catches typos and wrong collection namespaces before you chase playbook logic. Steps 3–4 separate parse errors from runtime module failures. Step 5 is the first verbosity level that usually shows enough SSH detail without drowning in connection plugin noise—use -vvvv only when -vvv still hides the handshake problem.
When the full playbook fails but ping succeeds, copy the failing task into a one-task play (or use --start-at-task) and rerun the same five steps against that smaller file.
Common Ansible Errors and Quick Fixes
| Error message | Usually means | Start here |
|---|---|---|
provided hosts list is empty |
Inventory not loaded or empty | Check -i inventory/hosts and run ansible-inventory --list |
Could not match supplied host pattern |
hosts: does not match inventory |
Run ansible-inventory --graph and --list-hosts |
UNREACHABLE! |
SSH, DNS, firewall, user, key, or port issue | Run ansible host -m ansible.builtin.ping -vvvv |
Permission denied (publickey,password) |
Wrong SSH user, password, or key | Check ansible_user, key path, and manual SSH |
Missing sudo password |
Become password required | Run with -K or configure sudoers carefully |
sudo: a password is required |
become: true but no password supplied |
Add --ask-become-pass or use NOPASSWD carefully |
mapping values are not allowed here |
YAML indentation, colon, or quoting issue | Run --syntax-check |
couldn't resolve module/action |
Missing collection, typo, or wrong module name | Use FQCN and install the collection |
Attempting to decrypt but no vault secrets found |
Vault password not supplied | Use --ask-vault-pass or --vault-id |
Decryption failed |
Wrong Vault password or Vault ID | Check vault ID and password source |
template error while templating string |
Jinja2 syntax, filter, or variable issue | Check template and variable values |
| Task changes every run | Non-idempotent task or volatile template content | See idempotency |
Inventory and Host Pattern Errors
These errors usually appear when Ansible never loads your inventory file, the hosts: line does not match any group or host name, or --limit narrows the run to zero targets. A typo in a group name (labs instead of lab) or running from the wrong directory without -i are frequent causes.
| Symptom | Common cause | First check |
|---|---|---|
provided hosts list is empty |
Wrong -i path, empty inventory, or command run outside the project |
ansible-inventory -i inventory/hosts --list |
Could not match supplied host pattern |
Typo in hosts: or group name |
ansible-inventory --graph |
| Play runs on zero hosts | --limit too narrow or pattern mismatch |
ansible-playbook -i inventory/hosts site.yml --list-hosts |
| Wrong host contacted | ansible_host override or DNS issue |
Inspect inventory host vars |
Confirm the play targets the hosts you expect before changing tasks. From the project directory, list which hosts match the play's hosts: pattern:
cd ~/ansible-projectansible-playbook -i inventory/hosts playbooks/ping-lab.yml --list-hostsSample output:
play #1 (lab): Ping lab hosts TAGS: []
pattern: ['lab']
hosts (1):
rocky2The hosts (1): line is what you want—a non-zero count means Ansible will run tasks on those machines.
When no host matches, Ansible stops before any task runs:
ERROR! Specified inventory, host pattern and/or --limit leaves us with no hosts to target.Fix the inventory path, correct the hosts: or --limit value, then rerun --list-hosts until the expected hostnames appear.
Inventory variables such as ansible_host, ansible_user, ansible_port, and ansible_connection change how Ansible connects—details live in inventory files and group_vars patterns.
SSH Connection Errors
UNREACHABLE! and Permission denied appear when Ansible cannot open an SSH session to the managed host—wrong IP or DNS in ansible_host, firewall blocking port 22, sshd down, incorrect ansible_user, or a key/password mismatch. When keys are not an option, password-based SSH with --ask-pass is covered in use Ansible with password. These failures happen before module logic runs, so fixing the task YAML will not help until connectivity works.
| Symptom | Common cause | First check |
|---|---|---|
UNREACHABLE! |
Network, SSH service, user, key, or port | Manual SSH, then ansible … -m ping -vvvv |
Permission denied (publickey,password) |
Wrong user or key | ansible_user, ansible_ssh_private_key_file |
| Connection timeout | Firewall, wrong IP, SSH down | ansible_host, test port 22 manually |
| Wrong user on host | Default remote_user vs inventory |
ansible-config dump --only-changed |
| Python interpreter error | Minimal image, wrong interpreter, or missing Python | ansible_python_interpreter; ansible -m setup -vvvv |
The ping module is not ICMP ping—it opens SSH and runs a small Python module on the managed host. Manual SSH can succeed while ansible.builtin.ping still fails when Python is missing or interpreter discovery picks the wrong binary. Set ansible_python_interpreter in inventory when needed, or install Python on minimal images before running modules.
Verify SSH outside Ansible with the same user and address from inventory:
ssh [email protected] echo okSample output:
okThen confirm Ansible can run a module on the host:
ansible -i inventory/hosts lab -m ansible.builtin.pingSample output:
rocky2 | SUCCESS => {
"changed": false,
"ping": "pong"
}When the address in inventory is wrong, the host is offline, or routing fails, you see UNREACHABLE! similar to:
rocky2 | UNREACHABLE! => {
"msg": "Data could not be sent to remote host \"192.168.56.199\". Make sure this host can be reached over ssh: ssh: connect to host 192.168.56.199 port 22: No route to host\r\n",
"unreachable": true
}Correct ansible_host, confirm the host is up, then rerun ping. For stubborn cases, add -vvvv and look for the SSH user and target IP Ansible actually used:
<192.168.56.109> ESTABLISH SSH CONNECTION FOR USER: ansible
<192.168.56.109> SSH: EXEC ssh ... -o 'User="ansible"' ... 192.168.56.109 ...If manual SSH works but Ansible fails, compare ansible_user, private key settings, and ansible.cfg remote_user against what succeeded on the command line.
sudo and become Errors
Package installs, service management, and writes under /etc fail with permission errors when become: true is missing or when the remote user must type a sudo password Ansible was not given. This often shows up after connectivity works—ping succeeds but dnf or service tasks fail.
| Symptom | Common cause | Fix |
|---|---|---|
Permission denied on /etc or package task |
Missing become: true |
Add become: true to play or task |
Missing sudo password |
sudo requires an interactive password | ansible-playbook site.yml -K |
sudo: a password is required |
Become enabled but no password supplied | --ask-become-pass or passwordless sudo for automation user |
user is not allowed to execute |
Remote user not in sudoers | Fix sudoers or use a user with privilege |
| Works as root manually but fails in Ansible | Ansible connects as a different user | Check ansible_user and become |
For package, service, and /etc changes, set become: true at the play level when most tasks need privilege:
- name: Configure managed host
hosts: lab
become: true
tasks:
- name: Install package
ansible.builtin.dnf:
name: httpd
state: presentansible.cfg may set become = True globally—if privileged tasks still fail, the ansible user likely needs NOPASSWD sudo or you must pass -K when sudo requires a password.
YAML and Playbook Syntax Errors
Syntax errors surface when you run ansible-playbook or --syntax-check—often after a rushed edit: module arguments placed on the same line as the module name, tabs mixed with spaces, or a missing - before a task. Ansible stops at parse time and points at the offending line.
| Error | When it usually appears |
|---|---|
mapping values are not allowed here |
Module params on the same line as module_name: |
did not find expected key |
Bad indentation under tasks: |
found character '\t' |
Tab characters in the YAML file |
| Task parsed as one long line | Missing - list marker before a task |
| Unexpected boolean | Unquoted yes/no/on/off treated as YAML booleans |
When YAML and structure are valid, syntax check prints only the playbook path:
ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --syntax-checkSample output:
playbook: playbooks/ping-lab.ymlWhen structure is wrong, Ansible names the line—here the mistake is inline msg: after ansible.builtin.debug: instead of an indented msg: key:
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
Syntax Error while loading YAML.
mapping values are not allowed in this context
The offending line appears to be:
- name: broken
ansible.builtin.debug: msg: hello
^ hereFix indentation and move module arguments under the module name, then rerun --syntax-check until it returns only the playbook path. Full YAML rules live in YAML syntax.
Variable, Collection and Module Errors
This section only covers first checks. If the error mentions couldn't resolve module/action, missing collection paths, FQCN, or undefined variables, use the dedicated collection and variable error guide for the full fix.
These messages appear after Ansible connects and parses the playbook: a typo in a module name, a collection missing from the control node, or a variable referenced before it is defined in inventory, play vars, or role defaults.
| Error | Typical scenario | Fix |
|---|---|---|
couldn't resolve module/action |
Typo or collection not installed on control node | FQCN + ansible-galaxy collection install |
| Collection installed but not found | Collection path in CI differs from laptop | Check COLLECTIONS_PATHS / collection paths from ansible --version |
| Works locally but fails in CI | Pipeline skipped requirements.yml |
Install collections before ansible-playbook |
'myvar' is undefined |
Var used in task before defined in scope | Define in inventory, play, or role defaults |
| Template variable missing | Jinja references unset fact or var | Debug value before template task |
A mistyped module name fails immediately:
rocky2 | FAILED! => {
"msg": "The module nonexistent.module was not found in configured module paths"
}Use the FQCN (ansible.builtin.copy, community.general.sefcontext) and confirm the collection is listed in ansible-galaxy collection list.
Referencing a variable that was never set in scope—for example in a command or template line—produces:
fatal: [rocky2]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'my_missing_var' is undefined."}Trace where the variable should come from (variables, group_vars, role defaults/) before changing the task. Deeper FQCN, path, and Jinja2 coverage is in collection and variable errors and collections overview.
Ansible Vault Errors
Vault errors appear when playbooks or group_vars contain encrypted content but no password or vault ID was supplied—common on first run, in CI without a secret, or after copying encrypted files without updating the password source.
| Error | Typical scenario | Fix |
|---|---|---|
Attempting to decrypt but no vault secrets found |
First run, CI job, or encrypted group_vars without a password |
--ask-vault-pass, --vault-password-file, or --vault-id |
Decryption failed |
Wrong password or vault ID label | Match vault ID in file header; re-enter correct password |
| Encrypted vars not loaded | Encrypted file outside Ansible search path | Place in group_vars, host_vars, or vars_files |
| CI cannot decrypt | Pipeline has no vault secret configured | Inject password file or env from CI secret store |
Example error:
Sample output:
ERROR! Attempting to decrypt but no vault secrets found
Start by rerunning the playbook with --ask-vault-pass or the correct --vault-id.Vault workflow—encrypting strings, editing encrypted files, and group_vars layout—is in Ansible Vault. This hub only maps error text to the missing password or file.
Template and Jinja2 Errors
Template and Jinja2 failures happen when Ansible renders a template file or evaluates Jinja2 expressions inside task arguments—syntax mistakes, undefined variables in {{ }}, or filters from a collection that is not installed. A template that embeds timestamps can also report changed on every run even when configuration is logically the same.
| Symptom | Typical scenario | Fix |
|---|---|---|
template error while templating string |
Bad {% %} / {{ }} or typo in expression |
Fix Jinja2 syntax in template |
| Undefined variable in template | Fact or var not set for that host | Define var or use default() only when optional |
| Missing filter | Filter lives in uninstalled collection | Install collection; use FQCN |
Template changed every run |
Timestamp or random value in rendered file | Remove volatile content from template |
Before a template task fails on an undefined variable, print the values the template depends on:
- name: Show template inputs
ansible.builtin.debug:
msg:
app_port: "{{ app_port | default('undefined') }}"
app_env: "{{ app_env | default('undefined') }}"Full debug, assert, and verbosity workflows live in debug Ansible playbooks.
File, Permission and Path Errors
These show up when a task runs but cannot read a source file, write a destination, or the service cannot use a path after copy—often missing become, wrong local vs remote path, or SELinux context blocking the daemon.
| Symptom | Typical scenario | Fix |
|---|---|---|
dest permission denied |
Writing to /etc without root |
become: true |
src file not found |
Path on control node does not exist | Fix path relative to playbook or role |
remote_src confusion |
Source file for copy exists on managed host, not control node |
Use remote_src: true with copy, or use fetch when pulling files back |
| Service cannot read file | SELinux type blocks daemon | Check ls -Z; see SELinux contexts |
| Wrong module for the job | copy vs template vs file |
Copy and fetch modules |
Use copy when the file starts on the control node and must go to the managed host. Use fetch when the file starts on the managed host and must come back to the control node.
For copy and template, src is normally read from the control node, while dest is written on the managed host. When the source file for copy already exists on the managed host, set remote_src: true.
When a symlink or copy references a path that does not exist yet, Ansible reports src file does not exist—fix task order so directories and files are created before dependent tasks run.
Debugging Failed Tasks
When connectivity, YAML, and variables look fine, inspect runtime state before rewriting the task:
| Tool | Use for |
|---|---|
ansible.builtin.debug |
Print vars and registered results |
register |
Capture module output for later tasks |
ansible.builtin.assert |
Fail early when a condition is wrong |
--check |
Dry-run supported modules |
--diff |
Show file changes with check mode |
--start-at-task |
Resume after a known-good task |
--step |
Confirm each task interactively |
-vvv |
Module payload and connection detail |
This section helps you choose the debugging tool. The next guide shows full examples with debug, assert, registered variables, facts, check mode, diff mode, and verbosity in debug Ansible playbooks.
Useful Commands for Ansible Troubleshooting
ansible --version
ansible-config dump --only-changed
ansible-inventory -i inventory/hosts --list
ansible-inventory -i inventory/hosts --graph
ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --list-hosts
ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --syntax-check
ansible-playbook -i inventory/hosts playbooks/ping-lab.yml --list-tasks
ansible-playbook -i inventory/hosts playbooks/file-demo.yml --check --diff
ansible -i inventory/hosts lab -m ansible.builtin.ping
ansible -i inventory/hosts rocky2 -m ansible.builtin.ping -vvvv
ansible-galaxy collection list
ansible-doc ansible.builtin.copy
ansible-vault view group_vars/all/vault.ymlRun these from the project directory that contains ansible.cfg so inventory and collection paths match your playbooks.
Best Practices to Avoid Ansible Errors
| Practice | Why |
|---|---|
| Start with inventory and connectivity checks | Avoids debugging the wrong layer |
Use --list-hosts before risky plays |
Confirms the play targets the right hosts |
Run --syntax-check after edits |
Catches YAML and structure errors early |
Use --check --diff for file changes |
Shows expected diffs where modules support it |
| Use FQCN for modules | Reduces collection ambiguity |
| Commit role and collection requirements | Makes CI and teammates reproducible |
| Keep Vault secrets out of plain vars | Prevents leakage in git |
Use debug and register during investigation |
Shows actual runtime values |
| Remove temporary debug tasks after fixing | Keeps playbooks clean |
Avoid broad ignore_errors: true |
Hides real failures |
Summary
Troubleshoot Ansible from the outside in: inventory and host patterns, SSH, sudo/become, YAML syntax, then variables, collections, Vault, templates, and finally task logic. Use the checklist commands and error tables to narrow the layer fast, then follow the dedicated guide for that topic. For runtime inspection, continue to debug Ansible playbooks; for couldn't resolve module/action and undefined variables in depth, see collection and variable errors.
References
- Ansible troubleshooting (community tips) — official tips
- Inventory — host patterns and variables
- Become (privilege escalation) — sudo and become flags
- Ansible inventory files — inventory design
- YAML syntax — indentation and quoting
- Ansible variables — variable basics
- Collections and RHEL System Roles — FQCN and install paths
- Ansible Vault — encrypt and decrypt workflow
- Template module — Jinja2 in playbooks

