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 block, rescue, always: Error Handling Complete Guide (2026)

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

Complete guide to Ansible block, rescue, and always for error handling. Implement try/catch logic, rollback failed tasks, clean up resources, and build.

Ansible's block, rescue, and always directives work like try/catch/finally in programming languages. They let you group tasks, handle errors gracefully, roll back failed changes, and ensure cleanup always runs — essential for production-grade playbooks.

Basic Syntax

- name: Handle errors with block/rescue/always
  block:
    # Tasks to try (like "try")
    - name: Task that might fail
      ansible.builtin.command: /usr/bin/risky-command

rescue: # Tasks to run if block fails (like "catch") - name: Handle the failure ansible.builtin.debug: msg: "The command failed, running recovery steps"

always: # Tasks that always run (like "finally") - name: Cleanup regardless of outcome ansible.builtin.debug: msg: "This always runs"

See also: Ansible changed_when & failed_when: Control Task Status (Guide)

block — Group Tasks Together

Even without rescue/always, block groups tasks and applies common directives:

- name: Install and configure nginx
  block:
    - name: Install nginx
      ansible.builtin.package:
        name: nginx
        state: present

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

- name: Start nginx ansible.builtin.service: name: nginx state: started enabled: true become: true when: install_nginx | default(true) tags: ['nginx']

All three tasks inherit become: true, when:, and tags: from the block.

rescue — Handle Failures

The rescue section runs only when a task in block fails:

- name: Deploy with rollback
  block:
    - name: Pull latest code
      ansible.builtin.git:
        repo: https://github.com/myorg/myapp.git
        dest: /opt/myapp
        version: "{{ deploy_version }}"

- name: Run database migrations ansible.builtin.command: cmd: /opt/myapp/manage.py migrate changed_when: true

- name: Restart application ansible.builtin.service: name: myapp state: restarted

rescue: - name: Rollback to previous version ansible.builtin.git: repo: https://github.com/myorg/myapp.git dest: /opt/myapp version: "{{ previous_version }}"

- name: Restart with previous version ansible.builtin.service: name: myapp state: restarted

- name: Send failure notification ansible.builtin.uri: url: "{{ slack_webhook }}" method: POST body: '{"text": "Deploy of {{ deploy_version }} failed on {{ inventory_hostname }}, rolled back"}' body_format: json

See also: Ansible retries & until: Retry Failed Tasks Automatically (Guide)

always — Guaranteed Cleanup

The always section runs regardless of success or failure:

- name: Database maintenance with guaranteed cleanup
  block:
    - name: Stop application
      ansible.builtin.service:
        name: myapp
        state: stopped

- name: Run database vacuum community.postgresql.postgresql_query: db: myapp query: "VACUUM FULL ANALYZE;"

rescue: - name: Log failure ansible.builtin.lineinfile: path: /var/log/maintenance.log line: "{{ ansible_date_time.iso8601 }} - Database maintenance FAILED" create: true

always: - name: Ensure application is running ansible.builtin.service: name: myapp state: started

- name: Log completion ansible.builtin.lineinfile: path: /var/log/maintenance.log line: "{{ ansible_date_time.iso8601 }} - Database maintenance completed" create: true

Access Error Information in rescue

Ansible provides special variables inside rescue:

- name: Handle errors with context
  block:
    - name: Risky operation
      ansible.builtin.command: /usr/bin/might-fail
      register: cmd_result

rescue: - name: Show which task failed ansible.builtin.debug: msg: | Failed task: {{ ansible_failed_task.name }} Failed result: {{ ansible_failed_result.msg | default('unknown') }} Host: {{ inventory_hostname }}

- name: Different handling based on error ansible.builtin.debug: msg: "Handling specific error" when: "'permission denied' in (ansible_failed_result.msg | default('') | lower)"

Available Variables in rescue

| Variable | Description | |----------|-------------| | ansible_failed_task | The task object that failed (name, action, etc.) | | ansible_failed_result | The result dict from the failed task (msg, rc, stderr, etc.) |

See also: Ansible troubleshooting - Error internal-error

Nested Blocks

Blocks can be nested for complex error handling:

- name: Multi-level error handling
  block:
    - name: Primary database
      block:
        - name: Connect to primary DB
          community.postgresql.postgresql_ping:
            db: myapp
            login_host: primary-db.example.com

rescue: - name: Failover to secondary DB block: - name: Connect to secondary DB community.postgresql.postgresql_ping: db: myapp login_host: secondary-db.example.com

rescue: - name: Both databases down ansible.builtin.fail: msg: "CRITICAL: Both primary and secondary databases are unreachable"

Real-World Patterns

Safe Service Restart

- name: Safe config update with validation
  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 validate: nginx -t -c %s

- name: Reload nginx ansible.builtin.service: name: nginx state: reloaded

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

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

- name: Report failure ansible.builtin.debug: msg: "Config update failed, reverted to backup"

always: - name: Remove backup file ansible.builtin.file: path: /etc/nginx/nginx.conf.bak state: absent

Health Check After Deploy

- name: Deploy with health check
  block:
    - name: Deploy new version
      ansible.builtin.include_role:
        name: deploy

- name: Wait for health check ansible.builtin.uri: url: "http://localhost:{{ app_port }}/health" status_code: 200 register: health retries: 10 delay: 5 until: health.status == 200

rescue: - name: Health check failed — rollback ansible.builtin.include_role: name: rollback

- name: Notify team ansible.builtin.mail: to: team@example.com subject: "Deploy FAILED on {{ inventory_hostname }}" body: "Health check failed after deploy. Rolled back automatically."

Temporary File Cleanup

- name: Process data with temp file cleanup
  block:
    - name: Create temp directory
      ansible.builtin.tempfile:
        state: directory
      register: temp_dir

- name: Download large file ansible.builtin.get_url: url: "{{ data_url }}" dest: "{{ temp_dir.path }}/data.tar.gz"

- name: Extract and process ansible.builtin.unarchive: src: "{{ temp_dir.path }}/data.tar.gz" dest: /opt/data/ remote_src: true

always: - name: Clean up temp directory ansible.builtin.file: path: "{{ temp_dir.path }}" state: absent when: temp_dir.path is defined

block vs ignore_errors

| Feature | block/rescue | ignore_errors: true | |---------|---------------|----------------------| | Error handling | Custom recovery logic | Silently continues | | Rollback | ✅ Yes | ❌ Manual only | | Error info | ansible_failed_task, ansible_failed_result | Only via register | | Cleanup | always section | Manual | | Play status | Rescued = success | Ignored = success | | Use when | Recovery steps needed | Error is acceptable |

Best Practices

Always use always for cleanup — temp files, locks, service states Don't overuse rescue — Only for real recovery logic, not suppressing errors Log failures in rescue — Don't silently swallow errors Test rescue paths — Force failures in staging to verify rollback works Use ansible_failed_task — Log which specific task failed for debugging Keep blocks focused — One logical operation per block, not entire playbooks

FAQ

What is block, rescue, always in Ansible?

They're Ansible's error handling mechanism, equivalent to try/catch/finally. block groups tasks to try, rescue runs if any block task fails, and always runs regardless of success or failure. Together they enable graceful error handling and guaranteed cleanup.

Does rescue run if any task in the block fails?

Yes, if any task in the block section fails, execution immediately jumps to rescue. Remaining block tasks are skipped. After rescue completes, the always section runs.

Can I use block without rescue?

Yes. block alone is useful for grouping tasks and applying shared directives (become, when, tags) to multiple tasks at once, even without error handling.

What happens if a task in rescue also fails?

The play fails for that host. If there's an always section, it still runs even when rescue fails. You can nest blocks inside rescue for multi-level error handling.

How do I re-raise an error after rescue?

Use ansible.builtin.fail at the end of your rescue section: fail: msg="Recovered but flagging failure". This marks the host as failed after your cleanup completes.

Conclusion

Ansible's block/rescue/always is essential for production playbooks: • block — Group tasks and apply shared directives • rescue — Run recovery steps when block fails • always — Guaranteed cleanup (temp files, services, locks) • Use ansible_failed_task/ansible_failed_result — Get error context in rescue

Every playbook that modifies production systems should use block/rescue/always for safe rollbacks and guaranteed cleanup.

Related Articles

Ansible Ignore Errors Complete GuideAnsible Error Handling: failed_when & changed_when GuideAnsible Check Mode (Dry Run) Complete GuideAnsible Handlers: Trigger Actions on Change

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home