Ansible: how to ignore unreachable hosts
Host wey Ansible mark UNREACHABLE no be failed task. Use ignore_unreachable, serial and max_fail_percentage, but still track wetin dem miss.
Host wey no reachable no mean say task fail
To make Ansible ignore hosts wey no reachable, set ignore_unreachable: true, and the switch go work. The important thing na to know when to use am, because Ansible dey handle two different problems in two different ways. Task wey run for host and return error na failure. Host wey Ansible no fit connect to at all na unreachable. ignore_errors only cover the first case. ignore_unreachable only cover the second case.
See the difference for play recap.
PLAY RECAP *********************************************************************
web1 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0Ansible connect to web1 and run seven tasks. web2 show unreachable=1 and failed=0, meaning say nothing run for that host at all. Ansible no ever get connection, so e remove the host from the play and continue with the remaining hosts. If that play dey install security update, one of your servers no get am.
Wetin dey make host unreachable
Unreachable mean say connection fail before any module reach the host. No module output dey to read. Na connection error only, and e go show for the first task wey touch the machine.
fatal: [web2]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.20 port 22: Connection refused", "unreachable": true}The msg field carry the real cause. Na these ones you go meet:
Connection refused: TCP connection reject, so nothing dey listen for that port. sshd fit don stop, or SSH fit don move go another port while your inventory still dey use 22.Connection timed out: Nothing answer at all. Firewall fit dey drop the packets, or server fit dey off. Each attempt go use the full connection timeout, wey be 10 seconds by default.Host key verification failed.: The key for~/.ssh/known_hostsno match the key wey server present. If dem rebuild VPS, e fit keep the same IP address but get new host key. So this one dey expected after reinstall, but e serious for any other time.Permission denied (publickey): SSH answer and reject your key. The port dey okay, so na authentication problem. Usually na wrongansible_useror key wey no load.Timeout (12s) waiting for privilege escalation prompt: Connection work, butbecomeno work. sudo dey wait for password wey no dey arrive.
People dey expect missing Python interpreter for that list, but e no belong there. SSH connect, so host dey reachable. The module then get nothing to run inside:
fatal: [db1]: FAILED! => {"changed": false, "module_stdout": "/bin/sh: 1: /usr/bin/python3: not found\r\n", "msg": "The module failed to execute correctly, you probably need to set the interpreter", "rc": 127}That line talk say FAILED!, and the recap count am under failed. So ignore_unreachable no go ever touch am. Set ansible_python_interpreter for that host, or install python3 for there.
Wetin dey make hosts wey no reachable ignore for play
For task level, the keyword dey beside the module:
- name: Read the package list, and do not stop if the host is down
ansible.builtin.command: dpkg -l
register: packages
changed_when: false
ignore_unreachable: trueFor play level, e set the default for every task inside the play. One task fit set am back:
- name: Opportunistic fleet maintenance
hosts: all
ignore_unreachable: true
tasks:
- name: This runs, cannot connect, and the play carries on
ansible.builtin.ping:
- name: This one still ends the play for a host that is down
ansible.builtin.ping:
ignore_unreachable: falseWetin dey happen underneath important to know. When ignore_unreachable dey set, host no longer comot from the play. So every later task go try connect again and fail again the same way. Each attempt go wait until connection timeout finish, 10 seconds unless you change timeout inside ansible.cfg. A twenty task play against one dead server fit add about 200 seconds to the run and twenty red lines to the log.
So check once, then stop that host cleanly:
- name: Opportunistic fleet maintenance
hosts: all
gather_facts: false
tasks:
- name: Check that the host answers before doing any work
ansible.builtin.ping:
register: reachable
ignore_unreachable: true
- name: End the play for this host if it never answered
ansible.builtin.meta: end_host
when: reachable.unreachable | default(false)
- name: Gather facts now that the connection is known good
ansible.builtin.setup:
- name: Refresh the package index
ansible.builtin.apt:
update_cache: true
become: trueThat one na one connection attempt for each dead host, instead of one attempt for every task. end_host, wey dem add for Ansible 2.8, ends the play for the current host without marking am as failed. The unreachable key dey for the registered result only when connection fail, so default(false) keep the condition valid for every host wey answer. Fact gathering dey off for play level because the implicit Gathering Facts task go otherwise be the task wey meet the broken connection. You want your own ping to do that check.
ignore_unreachable na play keyword and task keyword. Keep am for the playbook where reader fit see am, instead of inside role, because e decides which hosts the run fit miss. The difference between playbooks and roles explain which layer suppose own setting like this.
Why ignore_errors no be the correct tool for here
Ansible documentation talk plainly about the limit. ignore_errors "e only work when task fit run and return value of 'failed'. E no make Ansible ignore undefined variable errors, connection failures, execution issues (for example, missing packages), or syntax errors."
Connection failure no ever become task result with failed: true. E dey come as separate flag, and Ansible dey act on that flag first: host go enter unreachable list and comot from the play. Put ignore_errors: true for all twelve tasks of a play, and host wey SSH port close still go stop for the first one. Na this be the most common confusion for this area, and e worth make you grep your older playbooks for am, especially ones wey you write while learning how to write your first playbook against a VPS.
Debug before you suppress
Suppression wey become permanent na how fleet dey drift, because host wey nobody fit reach na still host wey nobody dey patch. Work through this order first. Every command here only dey read.
ansible web2 -i inventory.ini -m ansible.builtin.ping -odey run one module against one host and print one line.- Add
-vvvvto that same command. Ansible go print the full ssh command wey e build, including target user, port, private key and the options wey e pass. - Run that ssh command by yourself with
-v. If plain ssh no fit enter, the problem dey below Ansible and no playbook keyword go fix am. - Read the
msgstring and match am against the list above.Connection refusedandConnection timed outdey point to two different places, one for SSH service and one for network path. - For
Host key verification failed., check wetin you store withssh-keygen -F web2.example.com. If dem rebuild the server, remove the old entry withssh-keygen -R web2.example.comand accept the new key after you check am against the provider console. Settinghost_key_checking = Falseforansible.cfggo clear the error and also remove the check wey go tell you say different machine dey answer for that address now. - For
Permission denied (publickey), confirm wetin Ansible think say e suppose use.ansible-inventory -i inventory.ini --host web2go print the variables wey dey active, includingansible_userandansible_port. - If SSH dey work but modules no dey work, check the interpreter with
ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'. Therawmodule dey run command through the shell and e no need python for the target.
Na only after this you fit decide to ignore the host, instead of doing am out of habit.
Recap dey count unreachable separately, and CI usually miss am
ansible-playbook dey exit 0 when e succeed, 2 when at least one host fail, and 4 when at least one host unreachable. Those two values na bit flags for the source, so run wey get one failed host and one unreachable host go exit 6. The ansible command dey return the same codes. Dem check these codes against ansible-core source for August 2026.
Now set ignore_unreachable: true and run the same seven-task play against the same dead host:
PLAY RECAP *********************************************************************
web1 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=7web2 report unreachable=0 and seven tasks ok, and the run dey exit 0. When you set the keyword, Ansible dey increase the ok and ignored counters for that host instead of the counter wey e call dark, wey na the one wey dey fill the unreachable column. The red UNREACHABLE! lines still dey print, so the log dey show the truth, but the recap and exit code no dey.
CI job wey run the playbook and check only $? go call that run successful, and nothing for the summary go show say e never touch one machine. Make reachability check be separate step before the play:
ansible all -i inventory.ini -m ansible.builtin.ping -oThat one go print one line for each host and exit 4 if any host unreachable. This give the pipeline something to fail on and give you the host names for the log. ping need working Python interpreter for the target, so e prove more than only the connection, and na usually wetin you want. Then run the playbook with ignore_unreachable so hosts wey dey up still receive their change.
any_errors_fatal and max_fail_percentage for one batch
These play keywords decide wetin go happen after problem happen for part of the fleet, and dem no handle unreachable hosts the same way.
any_errors_fatal: true dey react when host no reachable. Ansible go finish the current task for the other hosts for the batch, then stop the play for every host wey dey inside am. Use am when the run only make sense if everything succeed or everything fail, like coordinated schema change.
max_fail_percentage: 30 no dey react when host no reachable. The check dey divide number of failed hosts by the batch size. Unreachable hosts dey separate list, so dem no dey enter that number. Ten hosts with four unreachable fit continue under max_fail_percentage: 10, but two hosts wey fail task go stop the play. The documentation add one more trap: "The percentage set must be exceeded, not equaled." With serial: 4, to stop after two failures out of four, write 49, no be 50.
One case dey where unreachable hosts fit stop run by themselves. If every host for the batch don fail or no reachable, Ansible get nothing left to work with and end the play with NO MORE HOSTS LEFT.
serial: change wey dey roll across the fleet
- name: Rolling nginx config update
hosts: webservers
serial: 2
max_fail_percentage: 25
tasks:
- name: Deploy the site config
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/conf.d/site.conf
owner: root
mode: "0644"
become: true
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
become: trueserial: 2 dey run the whole play against two hosts, e finish am, then e start the next two. serial: "25%" dey scale based on the group size. A list, serial: [1, 5, 10], na the canary pattern: one host first, then five, then ten, while any hosts wey remain dey run in batches of the last size. max_fail_percentage dey measure per batch, so the two work together. If the first machine break, the run stop before e break forty. Na this one make managing a fleet of Linux servers from one control machine safe to do with one command.
When to ignore unreachable hosts, and when not to
Ignore dem for opportunistic work. Fact collection run or hourly drift check no lose anything if e skip host wey dey down, because next pass go pick am up. Play level ignore_unreachable: true na correct answer for there, together with ping step so the skipped names go land somewhere wey person go read.
Never ignore dem for security patch run. The value of that run na the guarantee say every host get the fix, and suppressing the unreachable state dey turn “one server still vulnerable” into clean green recap. The host wey don unreachable for two weeks na the host wey likely dey far behind pass. Make that run exit 4, and make person check am.
One rule dey apply for both cases: suppress the stop, never suppress the record. If host skip, something must talk am, for recap, CI log, or monitoring alert. Ansible only know say host exist during the seconds wey play dey run against am, so e no be good place to learn say server don dey down since Tuesday. Monitoring na the work wey suppose handle that, and an Ansible playbook wey installs Zabbix fit give you fleet-wide view within one afternoon.
FAQ
Wetin be difference between ignore_errors and ignore_unreachable for Ansible?
ignore_errors: true dey apply to task wey run for host and return failure, like command wey exit with non-zero value. ignore_unreachable: true dey apply to host wey Ansible no fit connect to, where no module ever run. Dem dey read different fields for task result, and one no cover the other case. Ansible documentation talk say ignore_errors "no make Ansible ignore undefined variable errors, connection failures, execution issues (for example, missing packages), or syntax errors", and closed SSH port na connection failure.
Does ignore_unreachable hide host from play recap?
For practical purpose, yes. When you set the keyword, Ansible stop counting that host under unreachable and count am as ok and ignored once for each task, then the run exit 0. The fatal: [host]: UNREACHABLE! lines still print, so the log correct even though the recap and exit code no be. Watch the ignored column, or run ansible all -m ansible.builtin.ping -o as separate step so unreachable host still produce non-zero exit code somewhere.
Which exit code ansible-playbook return when host unreachable?
E return 4. Run wey get at least one failed host return 2, and the two values na bit flags, so run wey get both failure and unreachable host return 6. Clean run return 0. Dem check these codes against ansible-core source for August 2026. Setting ignore_unreachable: true remove the 4, na why pipeline wey test only exit code no fit see machine wey dem skip.
How I fit skip the rest of play for host wey never answer?
Make the first task ansible.builtin.ping with ignore_unreachable: true and register: reachable, then put ansible.builtin.meta: end_host after am under condition when: reachable.unreachable | default(false). end_host end the play for that host without marking am as failed. Set gather_facts: false for the play so your ping na the task wey meet the broken connection. Without this pattern, dead host remain for the play, and every later task go wait for connection timeout again.
I suppose ignore unreachable hosts during security patch run?
No. Patch run dey useful because e give you guarantee say every host get the update, and ignoring unreachable hosts replace that guarantee with green recap. Make the run exit 4, read the names of hosts wey no answer, and fix dem. Suppression belong to repeated opportunistic runs where the next pass go catch anything wey miss.