Using Ansible Block/Rescue/Always for Reliable Playbook Error Handling
Use Ansible’s block/rescue/always to run primary tasks, trigger a fallback on failure, and always clean up. Follow this guide to build robust playbooks that end in a known state.
24 Jun 2026, 17:05 UTC

Why Block/Rescue/Always Matters
When a playbook runs a series of system changes, a single failure can leave a host in a half‑applied state. Ansible’s block, rescue, and always constructs let you group tasks, define a fallback path, and guarantee cleanup. The goal is a predictable end‑state whether the main tasks succeed or not.
Prerequisites
- Ansible 2.5+ installed on the control node.
- SSH access to target hosts with key‑based authentication.
- An inventory file (e.g.,
inventory.ini) listing the hosts. - A text editor to create the playbook.
Desired Outcome
Execute a set of tasks (install a package, start a service). If any task fails, run a rescue block that rolls back the change or sends a notification. Regardless of success or failure, run an always block that cleans temporary files and logs the final status.
Step‑by‑Step Procedure
- Create the playbook file (e.g.,
rolling_update.yml). - Define the hosts and gather facts.
--- - name: Rolling update with error handling hosts: app_servers gather_facts: yes any_errors_fatal: false - Insert the block section with the primary tasks.
tasks: - block: - name: Install nginx apt: name: nginx state: present register: apt_result - name: Start nginx service: name: nginx state: started enabled: yes rescue: - name: Stop nginx if it started service: name: nginx state: stopped when: apt_result is defined and apt_result.changed - name: Send failure notification debug: msg: "Nginx install failed on {{ inventory_hostname }}" always: - name: Clean temporary files file: path: /tmp/rolling_update_{{ inventory_hostname }} state: absent - name: Log final status debug: msg: "Block execution finished on {{ inventory_hostname }}" - Run the playbook.
ansible-playbook rolling_update.yml -i inventory.iniUse
--checkto validate syntax without making changes:ansible-playbook --check rolling_update.yml.
Expected Checks and Verification
- When all block tasks succeed, the console should show the
Block execution finishedmessage and no rescue messages. - If the
apttask fails (e.g., package name typo), the rescue section should run, stopping nginx if it started and printing the debug notification. - The
alwayssection always runs, removing the temporary directory and logging the final status. - After the run, SSH into a host and confirm that
/tmp/rolling_update_{{ hostname }}no longer exists and that nginx is either running or stopped depending on the outcome.
Recovery Options
If the rescue section itself fails (e.g., the service module cannot stop nginx due to a lock), you can:
- Inspect the generated
rolling_update.retryfile and runansible-playbook --limit @rolling_update.retry rolling_update.ymlto target only the failing hosts. - Manually SSH into each host, verify the service state, and apply corrective actions (e.g.,
systemctl stop nginx). - Re‑run the full playbook after fixing the underlying issue.
Limitations and Caveats
- Tasks marked with
ignore_errors: truebypass rescue; use them only when you truly want to ignore failures. - Asynchronous tasks (e.g.,
asyncwithpoll: 0) do not trigger rescue if they return a non‑zero exit code; you must handle them separately. - The
alwaysblock is not executed if the playbook is killed withSIGKILLor the control node loses power. - Rescue sections run only when a task returns a non‑zero exit code; warnings or informational errors do not trigger rescue.
Practical Checklist Before Running
| Item | Check |
|---|---|
| Inventory file exists | File path correct and hosts reachable via SSH |
| Playbook syntax | Run ansible-playbook --syntax-check rolling_update.yml |
| Permissions | Control node user can SSH as the target user and has sudo rights if needed |
| Module availability | Modules used (apt, service, file) exist on the target OS version |
| Rescue logic | Verify that the condition (e.g., apt_result.changed) correctly identifies when a rollback is necessary |
Conclusion
By encapsulating critical tasks inside a block and pairing it with rescue and always sections, you give your playbooks a safety net. The system ends in a known state, and you gain clear visibility into what happened during the run. This pattern scales from simple package installs to complex rolling upgrades.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.