Ansible fail Module — Abort Playbooks with Custom Messages

Introduction

ansible.builtin.fail intentionally stops playbook execution with a custom error message. Use it to enforce preconditions, validate inputs, and prevent playbooks from running in unsafe conditions. It's the "abort" button for Ansible — a clear way to say "something is wrong, stop here."

Basic Usage

---
- name: Fail module examples
  hosts: all
  tasks:
    - name: Fail unconditionally
      ansible.builtin.fail:
        msg: "This playbook is deprecated. Use deploy-v2.yml instead."

    - name: Fail with condition
      ansible.builtin.fail:
        msg: "This playbook only runs on Debian/Ubuntu systems"
      when: ansible_os_family != "Debian"

Input Validation

    - name: Require version variable
      ansible.builtin.fail:
        msg: "Variable 'app_version' is required. Use -e app_version=x.y.z"
      when: app_version is not defined

    - name: Validate version format
      ansible.builtin.fail:
        msg: "Invalid version '{{ app_version }}'. Expected format: x.y.z"
      when: app_version is not regex('^[0-9]+\.[0-9]+\.[0-9]+$')

    - name: Validate environment
      ansible.builtin.fail:
        msg: "Invalid environment '{{ env }}'. Must be: dev, staging, or production"
      when: env not in ['dev', 'staging', 'production']

Precondition Checks

    - name: Check minimum disk space
      ansible.builtin.command:
        cmd: df --output=avail / | tail -1
      register: disk_avail
      changed_when: false

    - name: Fail if disk space too low
      ansible.builtin.fail:
        msg: "Only {{ (disk_avail.stdout | int / 1024) | int }}MB available. Need at least 2048MB."
      when: (disk_avail.stdout | int / 1024) < 2048

    - name: Check minimum memory
      ansible.builtin.fail:
        msg: "Only {{ ansible_memtotal_mb }}MB RAM. Minimum 4096MB required."
      when: ansible_memtotal_mb < 4096

    - name: Check Python version
      ansible.builtin.fail:
        msg: "Python 3.9+ required. Found: {{ ansible_python_version }}"
      when: ansible_python_version is version('3.9', '<')

Production Guardrails

    - name: Prevent accidental production runs
      ansible.builtin.fail:
        msg: |
          ⚠️  PRODUCTION DEPLOYMENT BLOCKED
          
          You must set confirm_production=yes to deploy to production.
          Run: ansible-playbook deploy.yml -e confirm_production=yes -e env=production
      when:
        - env == 'production'
        - confirm_production | default('no') != 'yes'

    - name: Block deployments during maintenance window
      ansible.builtin.fail:
        msg: "Deployments blocked during maintenance window ({{ maintenance_start }} - {{ maintenance_end }})"
      when:
        - maintenance_mode | default(false)

fail vs assert

    # fail — single condition, custom message
    - name: Check with fail
      ansible.builtin.fail:
        msg: "Port 8080 is already in use"
      when: port_check.rc == 0

    # assert — multiple conditions, cleaner for validation
    - name: Check with assert
      ansible.builtin.assert:
        that:
          - app_version is defined
          - env in ['dev', 'staging', 'production']
          - ansible_memtotal_mb >= 4096
          - ansible_os_family == 'Debian'
        fail_msg: "Precondition check failed. See conditions above."
        success_msg: "All preconditions met."
Featurefailassert
Multiple conditionsNeeds multiple tasksSingle task with list
Custom per-condition message✅ Each task has its ownOne message for all
ReadabilityBetter for complex messagesBetter for checklists
Success message❌✅ success_msg

In block/rescue

    - name: Deployment with validation
      block:
        - name: Deploy application
          ansible.builtin.command:
            cmd: /opt/deploy.sh {{ app_version }}

        - name: Verify health
          ansible.builtin.uri:
            url: "http://localhost:8080/health"
            status_code: 200

      rescue:
        - name: Rollback
          ansible.builtin.command:
            cmd: /opt/deploy.sh rollback

        - name: Fail with context
          ansible.builtin.fail:
            msg: |
              Deployment of v{{ app_version }} FAILED on {{ inventory_hostname }}.
              Rollback completed. Check application logs at /var/log/myapp/.

Common Patterns

Cluster Quorum Check

    - name: Count available nodes
      ansible.builtin.set_fact:
        available_nodes: "{{ groups['db_cluster'] | map('extract', hostvars) | selectattr('ansible_host', 'defined') | list | length }}"

    - name: Ensure quorum before maintenance
      ansible.builtin.fail:
        msg: "Only {{ available_nodes }}/{{ groups['db_cluster'] | length }} nodes available. Need quorum ({{ (groups['db_cluster'] | length // 2) + 1 }})."
      when: available_nodes | int < (groups['db_cluster'] | length // 2) + 1

Incompatible Versions

    - name: Check Ansible version
      ansible.builtin.fail:
        msg: "This playbook requires Ansible 2.15+. You have {{ ansible_version.full }}."
      when: ansible_version.full is version('2.15', '<')

Troubleshooting

IssueSolution
Fail runs when it shouldn'tCheck when condition logic (and/or/not)
Want to fail but continue other hostsUse ignore_errors on the fail task (unusual)
Need to fail ALL hosts at onceUse any_errors_fatal: true on the play
Multi-line message not formattingUse `

Best Practices

  1. Validate inputs early — fail at the start, not after 20 tasks
  2. Use descriptive messages — include what's wrong AND how to fix it
  3. Include variable values — "Expected 4096MB, got {{ ansible_memtotal_mb }}MB"
  4. Use assert for checklists — multiple conditions in one task
  5. Add production guardrails — require explicit confirmation for dangerous operations
  6. Combine with block/rescue — rollback then fail with context

Conclusion

ansible.builtin.fail is your playbook's safety net. Use it early to validate inputs, check preconditions, and prevent automation from running in unsafe conditions. A clear error message like "Only 1024MB RAM available, need 4096MB" saves hours of debugging cryptic failures halfway through a deployment.