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

DirectiveScopeUse Case
ignore_errors: trueSingle taskOptional tasks, skip non-critical failures
failed_whenSingle taskCustom failure conditions
block/rescue/alwaysGroup of tasksTry/catch/finally pattern
ignore_unreachablePlay or taskHandle offline hosts
any_errors_fatalPlayCritical operations — stop everything on first failure
max_fail_percentagePlayRolling 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.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home