That moment when your playbook fails with The task includes an option with an undefined variable is frustrating. You defined the variable, or at least you thought you did. The error message doesn't always make it clear where things went wrong.

Let me show you how to systematically diagnose and fix undefined variable errors.

Understanding the Error

A typical undefined variable error looks like this:

bash
TASK [Deploy application] *****************************************************
fatal: [webserver01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'app_version' is undefined\n\nThe error appears to be in '/home/user/playbooks/deploy.yml': line 15, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Deploy application\n  ^ here\n"}

The key information: 'app_version' is undefined. Ansible looked for this variable and couldn't find it in any scope.

Step 1: Check Variable Definition Location

First, verify where you expected the variable to come from. Ansible variables can be defined in many places:

bash
# Check if it's in your playbook
grep -r "app_version" playbooks/

Common locations: - group_vars/all.yml - group_vars/webservers.yml - host_vars/webserver01.yml - Playbook vars: section - Role defaults/main.yml or vars/main.yml - Command line with -e

Step 2: Debug Variable Values

Use the debug module to inspect what Ansible actually sees:

yaml
- name: Debug variable value
  debug:
    var: app_version

Run it:

bash
ansible-playbook -i inventory deploy.yml -t debug

If the output shows app_version: VARIABLE IS NOT DEFINED!, you know Ansible cannot find it anywhere.

For a comprehensive view of all variables for a host:

yaml
- name: Show all variables
  debug:
    var: vars

Or use the setup module to see facts:

bash
ansible webserver01 -i inventory -m setup | grep -i app

Step 3: Understand Variable Precedence

Ansible has 22 levels of variable precedence. The most important ones from lowest to highest:

  1. 1.Role defaults (roles/x/defaults/main.yml)
  2. 2.Inventory file or script group vars
  3. 3.Inventory group_vars/all.yml
  4. 4.Playbook group_vars
  5. 5.Inventory host_vars
  6. 6.Playbook host_vars
  7. 7.Host facts
  8. 8.Play vars
  9. 9.Play vars_prompt
  10. 10.Role vars (roles/x/vars/main.yml)
  11. 11.Block vars
  12. 12.Task vars
  13. 13.Include_vars
  14. 14.Role params
  15. 15.Extra vars (-e "key=value")

This means if you define app_version in both group_vars/all.yml and pass -e "app_version=2.0", the extra var wins.

To see the effective value considering precedence:

yaml
- name: Show variable with all sources
  debug:
    msg: "app_version is {{ app_version }}"
  vars:
    app_version: "override"

Step 4: Fix Missing Variables with Defaults

The safest approach for variables that might not be defined is using the default filter:

yaml
- name: Deploy with default version
  debug:
    msg: "Deploying version {{ app_version | default('latest') }}"

For complex defaults:

yaml
- name: Configure application
  template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  vars:
    db_host: "{{ db_host | default('localhost') }}"
    db_port: "{{ db_port | default(5432) }}"

Step 5: Handle Registered Variables

If the variable comes from a previous task's register, ensure that task runs first:

```yaml - name: Get current version shell: cat /opt/app/version.txt register: current_version changed_when: false

  • name: Show version
  • debug:
  • var: current_version.stdout
  • `

If the first task fails or is skipped, current_version won't be defined properly. Always check:

yaml
- name: Show version safely
  debug:
    msg: "Current version: {{ current_version.stdout | default('unknown') }}"
  when: current_version is defined

Step 6: Check Inventory Variable Syntax

Inventory variables have specific syntax requirements. In INI format:

```ini [webservers] webserver01 app_version=1.2.3

[webservers:vars] env=production ```

In YAML format:

yaml
all:
  children:
    webservers:
      hosts:
        webserver01:
          app_version: "1.2.3"
      vars:
        env: production

A common mistake is mixing formats or having syntax errors that silently fail:

bash
ansible-inventory -i inventory --list

This validates your inventory and shows the parsed variables.

Step 7: Handle Nested Dictionary Access

Accessing nested undefined variables requires special handling:

yaml
# This fails if config is undefined
- name: Bad nested access
  debug:
    msg: "{{ config.database.host }}"

Use the | default({}) pattern:

yaml
- name: Safe nested access
  debug:
    msg: "{{ (config | default({})).get('database', {}).get('host', 'localhost') }}"

Or in Jinja2:

yaml
- name: Safe nested access with Jinja
  debug:
    msg: "{{ config.database.host | default('localhost') }}"
  when: config is defined and config.database is defined

Step 8: Debug Variable Scope Issues

Sometimes variables are defined but in the wrong scope. Use ansible.builtin.vars lookup:

yaml
- name: Check if variable exists in any scope
  debug:
    msg: "Variable exists: {{ lookup('vars', 'app_version', default='not found') }}"

To list all variables at runtime:

bash
ansible-playbook deploy.yml --step

Then at the step prompt, you can inspect variables.

Step 9: Conditional Variable Definition

Define variables conditionally based on other variables:

yaml
- name: Set version based on environment
  set_fact:
    app_version: "{{ '2.0.0' if env == 'production' else '2.0.0-dev' }}"
  when: app_version is not defined

Or use a dedicated variables file:

yaml
- name: Load environment variables
  include_vars: "{{ env }}.yml"

Where production.yml contains:

yaml
app_version: "2.0.0"
db_host: "prod-db.example.com"

Quick Verification

After fixes, verify your variable is properly defined:

bash
ansible webserver01 -i inventory -m debug -a "var=app_version"

Or test the entire playbook with check mode:

bash
ansible-playbook -i inventory deploy.yml --check

Best Practices

  1. 1.Use role defaults for fallback values:
yaml
# roles/app/defaults/main.yml
app_version: "latest"
app_port: 8080
  1. 1.Validate required variables at play start:
yaml
- name: Validate required variables
  assert:
    that:
      - app_version is defined
      - db_host is defined
    fail_msg: "Required variables are not defined"
    success_msg: "All required variables are present"
  1. 1.Document expected variables in playbook comments:
yaml
# Required variables:
# - app_version: Version to deploy
# - deploy_target: Target environment (staging/production)

Undefined variable errors are almost always simple oversights. The key is understanding where Ansible looks for variables and ensuring your definitions are in the right place with the right precedence.