AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible ignore_errors: Skip Task Failures & Continue Playbook (Guide)

By Luca Berton · Published 2024-01-01 · Category: installation

How to handle errors in Ansible with ignore_errors, failed_when, block/rescue/always. Control task failure behavior, continue on error.

When an Ansible task fails, the entire playbook stops for that host. Sometimes that's what you want. Sometimes you need to continue — clean up after failures, try alternatives, or skip optional steps. Here's every way to handle errors in Ansible.

ignore_errors: true

The simplest approach — the task can fail and the playbook continues:

- name: Check if old service exists (may not be installed)
  ansible.builtin.command: systemctl status legacy-app
  register: legacy_status
  ignore_errors: true

- name: Stop old service if it was running ansible.builtin.service: name: legacy-app state: stopped when: legacy_status.rc == 0

⚠️ The problem with ignore_errors: It swallows ALL failures, including unexpected ones. Your playbook reports "ok" even when something is genuinely broken.

See also: Ansible ignore_errors: Error Handling Best Practices (Complete Guide)

failed_when: Custom Failure Conditions

More precise than ignore_errors — define exactly what counts as failure:

- name: Check disk space
  ansible.builtin.command: df -h /data
  register: disk_check
  failed_when: "'100%' in disk_check.stdout"

- name: Run database query (exit code 1 means "no rows", not error) ansible.builtin.command: /opt/db/check-pending-jobs.sh register: job_check failed_when: job_check.rc not in [0, 1] # rc=0: jobs found, rc=1: no jobs (ok), rc=2+: actual error

Common failed_when Patterns

# Fail only on specific return codes
failed_when: result.rc not in [0, 2]

# Fail if output contains error message failed_when: "'ERROR' in result.stdout"

# Fail if command took too long (indicates problem) failed_when: result.delta | regex_search('[0-9]+') | int > 30

# Never fail (same as ignore_errors, but more explicit) failed_when: false

# Multiple conditions failed_when: - result.rc != 0 - "'already exists' not in result.stderr"

changed_when: Control Change Status

Not error handling per se, but often paired with it:

- name: Check if reboot is needed
  ansible.builtin.command: needs-restarting -r
  register: reboot_check
  changed_when: reboot_check.rc == 1
  failed_when: reboot_check.rc > 1

See also: Ansible ignore_errors: Handle Task Failures (Complete Guide)

block/rescue/always: Try-Catch

The most powerful pattern — Ansible's equivalent of try/catch/finally:

- name: Deploy with rollback
  block:
    - name: Deploy new version
      ansible.builtin.copy:
        src: "app-v{{ new_version }}.tar.gz"
        dest: /opt/app/
      
    - name: Extract and install
      ansible.builtin.unarchive:
        src: "/opt/app/app-v{{ new_version }}.tar.gz"
        dest: /opt/app/current/
        remote_src: true

- name: Run health check ansible.builtin.uri: url: http://localhost:8080/health status_code: 200 retries: 5 delay: 10

rescue: - name: Deployment failed — rolling back ansible.builtin.debug: msg: "⚠️ Deployment failed on {{ inventory_hostname }}, rolling back..."

- name: Restore previous version ansible.builtin.copy: src: "/opt/app/backup/app-v{{ current_version }}.tar.gz" dest: /opt/app/ - name: Restart with old version ansible.builtin.service: name: myapp state: restarted

always: - name: Clean up temp files ansible.builtin.file: path: /tmp/deploy-staging state: absent

- name: Send notification ansible.builtin.uri: url: "https://hooks.slack.com/services/xxx" method: POST body_format: json body: text: "Deploy {{ new_version }} on {{ inventory_hostname }}: {{ 'FAILED (rolled back)' if ansible_failed_task is defined else 'SUCCESS' }}"

ignore_unreachable

For hosts that might be offline:

- name: Check all servers (some may be down)
  hosts: all
  ignore_unreachable: true
  tasks:
    - name: Ping
      ansible.builtin.ping:
      register: ping_result

- name: Only run on reachable hosts ansible.builtin.debug: msg: "{{ inventory_hostname }} is alive" when: ping_result is not failed

See also: Ansible block, rescue, always: Error Handling Complete Guide (2026)

any_errors_fatal

The opposite of ignore_errors — fail the ENTIRE play if ANY host fails:

- name: Critical database migration
  hosts: dbservers
  any_errors_fatal: true    # If one DB fails, stop ALL hosts
  tasks:
    - name: Run migration
      ansible.builtin.command: /opt/db/migrate.sh

max_fail_percentage

Stop the play if too many hosts fail:

- name: Rolling update
  hosts: webservers
  serial: 5
  max_fail_percentage: 20    # Stop if >20% of batch fails
  tasks:
    - name: Deploy
      ansible.builtin.copy:
        src: app.tar.gz
        dest: /opt/app/

Pattern: Optional Task

# Only run if the tool exists
- name: Clear application cache (if artisan exists)
  ansible.builtin.command: php artisan cache:clear
  args:
    chdir: /var/www/app
  register: cache_clear
  failed_when:
    - cache_clear.rc != 0
    - "'not found' not in cache_clear.stderr"

Pattern: First Available

- name: Try multiple package managers
  block:
    - name: Try apt
      ansible.builtin.apt:
        name: nginx
        state: present
  rescue:
    - name: Try dnf
      ansible.builtin.dnf:
        name: nginx
        state: present

Pattern: Validate Before and After

- name: Safe config update
  block:
    - name: Backup current config
      ansible.builtin.copy:
        src: /etc/nginx/nginx.conf
        dest: /etc/nginx/nginx.conf.bak
        remote_src: true

- name: Deploy new config ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf

- name: Test config ansible.builtin.command: nginx -t - name: Reload nginx ansible.builtin.service: name: nginx state: reloaded

rescue: - name: Restore backup ansible.builtin.copy: src: /etc/nginx/nginx.conf.bak dest: /etc/nginx/nginx.conf remote_src: true

- name: Reload with old config ansible.builtin.service: name: nginx state: reloaded

- name: Fail with clear message ansible.builtin.fail: msg: "Nginx config update failed — rolled back to previous version"

Comparison Table

| Directive | Scope | Use Case | |-----------|-------|----------| | ignore_errors: true | Single task | Optional tasks, skip non-critical failures | | failed_when | Single task | Custom failure conditions | | block/rescue/always | Group of tasks | Try/catch/finally pattern | | ignore_unreachable | Play or task | Handle offline hosts | | any_errors_fatal | Play | Critical operations — stop everything on first failure | | max_fail_percentage | Play | Rolling updates — abort if too many fail |

FAQ

When should I use ignore_errors vs failed_when?

Use failed_when whenever possible — it's more precise. ignore_errors: true hides all failures; failed_when lets you define exactly what constitutes a real failure vs expected behavior. Example: a command returning exit code 1 might mean "not found" (acceptable) vs exit code 2 meaning actual error.

Does ignore_errors affect handlers?

No. If a task with ignore_errors: true also has notify, the handler is NOT notified when the task fails. The task must succeed to trigger handlers.

Can I use block/rescue across multiple plays?

No. block/rescue/always works within a single play. For cross-play error handling, use meta: end_play or set facts that subsequent plays check.

How do I make a playbook continue but report failures at the end?

Register each risky task's result, continue with ignore_errors, then fail at the end:

- name: Risky task
  ansible.builtin.command: /opt/check.sh
  register: check_result
  ignore_errors: true

# ... more tasks ...

- name: Fail if any checks failed ansible.builtin.fail: msg: "Check failed: {{ check_result.stderr }}" when: check_result is failed

Conclusion

Start with failed_when for precise failure control. Use block/rescue/always for operations that need rollback. Reserve ignore_errors: true for truly optional tasks. And use any_errors_fatal for critical operations where one failure means everything should stop.

Related Articles

Ansible Check Mode and Diff ModeAnsible debug and assert GuideAnsible Idempotent PlaybooksAnsible block/rescue/always Guide

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home