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 Undefined Variable Error: 12 Real Examples and Fixes

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

Fix Ansible undefined variable errors with 12 real-world examples. Covers 'variable is undefined', typos, scope issues, missing defaults, hostvars lookups.

"variable_name" is undefined is the most common Ansible error. It surfaces in dozens of ways — from simple typos to scope misunderstandings to missing host facts. This guide covers 12 real examples with exact error messages and fixes.

Error Format

fatal: [hostname]: FAILED! => {
    "msg": "The task includes an option with an undefined variable.
    The error was: 'my_variable' is undefined.
    ...

See also: AAP 2.6 Troubleshooting Guide: Common Issues and Solutions

Example 1: Simple Typo

The most common cause. A variable is defined as db_host but referenced as db_hostname.

# ❌ FAILS
- ansible.builtin.debug:
    msg: "Database: {{ db_hostname }}"
  vars:
    db_host: "10.0.1.10"

# ✅ FIX — match the variable name - ansible.builtin.debug: msg: "Database: {{ db_host }}" vars: db_host: "10.0.1.10"

Tip: Use grep -rn "db_host" roles/ group_vars/ host_vars/ to find the actual variable name.

Example 2: Variable Not in Scope

Variables defined in one play aren't available in another.

# ❌ FAILS — play_var not available in Play 2
- name: Play 1
  hosts: webservers
  vars:
    play_var: "hello"
  tasks:
    - ansible.builtin.debug:
        msg: "{{ play_var }}"

- name: Play 2 hosts: dbservers tasks: - ansible.builtin.debug: msg: "{{ play_var }}" # UNDEFINED!

# ✅ FIX — use set_fact to persist, or move to group_vars - name: Play 1 hosts: webservers tasks: - ansible.builtin.set_fact: shared_var: "hello"

- name: Play 2 hosts: dbservers tasks: - ansible.builtin.debug: msg: "{{ hostvars[groups['webservers'][0]].shared_var }}"

See also: Ansible Troubleshooting: Common Errors, Solutions, and Debug Techniques

Example 3: Missing Default Value

A variable expected from group_vars or host_vars doesn't exist for some hosts.

# ❌ FAILS — http_port not defined for all hosts
- name: Configure service
  ansible.builtin.template:
    src: config.j2
    dest: /etc/myapp.conf
  # config.j2 uses {{ http_port }}

# ✅ FIX — use default() filter in template # config.j2: # port = {{ http_port | default(8080) }}

# ✅ Or set a default in the playbook - name: Configure service ansible.builtin.template: src: config.j2 dest: /etc/myapp.conf vars: http_port: "{{ http_port | default(8080) }}"

Example 4: Fact Not Gathered

Using ansible_distribution or other facts when gather_facts: false.

# ❌ FAILS
- hosts: all
  gather_facts: false
  tasks:
    - ansible.builtin.debug:
        msg: "OS: {{ ansible_distribution }}"

# ✅ FIX — gather facts explicitly - hosts: all gather_facts: false tasks: - ansible.builtin.setup: gather_subset: min

- ansible.builtin.debug: msg: "OS: {{ ansible_distribution }}"

See also: Managing ABRT Debug Files: Clean Up Disk Space on Fedora

Example 5: Registered Variable Used Before Task Runs

# ❌ FAILS — result not yet registered when skipped
- ansible.builtin.command: whoami
  register: result
  when: false

- ansible.builtin.debug: msg: "{{ result.stdout }}" # result exists but result.stdout doesn't

# ✅ FIX — check if variable is defined and has expected key - ansible.builtin.debug: msg: "{{ result.stdout | default('not run') }}" when: result is defined and result is not skipped

Example 6: hostvars Lookup for Offline Host

# ❌ FAILS — if db_server not in current play
- ansible.builtin.debug:
    msg: "DB IP: {{ hostvars['db_server'].ansible_default_ipv4.address }}"

# ✅ FIX — ensure facts are gathered, or use default - ansible.builtin.debug: msg: "DB IP: {{ hostvars['db_server'].ansible_default_ipv4.address | default('unknown') }}"

Example 7: Loop Variable Outside Loop

# ❌ FAILS — item only exists inside the loop
- ansible.builtin.debug:
    msg: "Installing {{ item }}"
  loop:
    - nginx
    - redis

- ansible.builtin.debug: msg: "Last installed: {{ item }}" # UNDEFINED!

# ✅ FIX — register loop results - ansible.builtin.debug: msg: "Installing {{ item }}" loop: - nginx - redis register: install_results

- ansible.builtin.debug: msg: "Installed: {{ install_results.results | map(attribute='item') | list }}"

Example 8: Role Default Not Loaded

# ❌ FAILS — role defaults only load when role is included
# In a standalone playbook using a template from a role:
- ansible.builtin.template:
    src: roles/myrole/templates/config.j2
    dest: /etc/config
  # Template references {{ myrole_port }} defined in roles/myrole/defaults/main.yml
  # But defaults aren't loaded because the role isn't included!

# ✅ FIX — include the role properly - ansible.builtin.include_role: name: myrole

Example 9: Conditional Prevents set_fact

# ❌ FAILS — set_fact skipped, variable never created
- ansible.builtin.set_fact:
    deploy_path: "/opt/app"
  when: ansible_os_family == "Debian"

- ansible.builtin.debug: msg: "Deploying to {{ deploy_path }}" # Undefined on non-Debian!

# ✅ FIX — set a default before the conditional - ansible.builtin.set_fact: deploy_path: "/usr/local/app"

- ansible.builtin.set_fact: deploy_path: "/opt/app" when: ansible_os_family == "Debian"

Example 10: Dictionary Key Doesn't Exist

# ❌ FAILS — 'address' key missing from dict
- ansible.builtin.debug:
    msg: "{{ network_config.address }}"
  vars:
    network_config:
      interface: eth0
      # address key missing!

# ✅ FIX — use default() or check first - ansible.builtin.debug: msg: "{{ network_config.address | default('dhcp') }}"

# ✅ Or use dict.get() syntax - ansible.builtin.debug: msg: "{{ network_config['address'] | default('dhcp') }}"

Example 11: Extra Vars Expected but Not Passed

# ❌ FAILS when run without -e
# ansible-playbook deploy.yml (missing -e version=1.2.3)
- name: Deploy version {{ version }}
  ansible.builtin.debug:
    msg: "Deploying {{ version }}"

# ✅ FIX — validate required variables - name: Validate required variables ansible.builtin.assert: that: - version is defined - version | length > 0 fail_msg: "Run with: ansible-playbook deploy.yml -e version=X.Y.Z"

- name: Deploy version {{ version }} ansible.builtin.debug: msg: "Deploying {{ version }}"

Example 12: Undefined in Jinja2 Template File

# Error in template, not playbook
# templates/config.j2 references {{ app_secret }}
# but app_secret is only in vault, and vault file wasn't loaded

# ✅ FIX — ensure vault file is loaded # ansible-playbook site.yml --ask-vault-pass # or # ansible-playbook site.yml --vault-password-file ~/.vault_pass

The default() Filter: Your Best Friend

# Simple default
{{ my_var | default("fallback_value") }}

# Default for undefined AND empty {{ my_var | default("fallback", true) }}

# Nested default {{ config.database.host | default("localhost") }}

# Boolean default {{ enable_feature | default(false) }}

# List default {{ allowed_hosts | default([]) }}

# Dict default {{ extra_config | default({}) }}

ansible-core 2.19 Changes

Data Tagging in ansible-core 2.19 can surface additional undefined variable scenarios:

# ❌ NEW in 2.19 — template delimiters catch undefined differently
- ansible.builtin.debug:
    msg: "{{ '{{ undefined_var }}' }}"
# Previously: silently rendered as empty string
# Now: may error on undefined

# ✅ FIX — reference variables directly - ansible.builtin.debug: msg: "{{ undefined_var | default('not set') }}"

Debugging Tips

# Show all variables for a host
ansible hostname -m debug -a "var=hostvars[inventory_hostname]"

# Check if variable exists ansible hostname -m debug -a "var=my_variable"

# Run with extra verbosity ansible-playbook site.yml -vvv

# Print all variables in a task - ansible.builtin.debug: var: vars

FAQ

What's the difference between "is undefined" and "is not defined"?

They mean the same thing. is undefined and is not defined are both valid Jinja2 tests. Use whichever reads more naturally: when: my_var is defined or when: my_var is not undefined.

How do I set a default for a nested dictionary key?

Use the default() filter at each level: {{ config.database.host | default('localhost') }}. For deeply nested structures, consider {{ (config.database | default({})).host | default('localhost') }}.

Why does my variable work in one play but not another?

Variables have scope. Play-level vars: only apply to that play. Use set_fact to persist variables across plays (via hostvars), or define shared variables in group_vars/all/.

How do I make a variable required (fail if missing)?

Use ansible.builtin.assert at the start of your playbook to validate all required variables exist before proceeding.

Conclusion

Undefined variable errors have a finite set of causes: typos, scope issues, missing defaults, ungathered facts, or conditional execution. Use the default() filter defensively, validate required variables with assert, and check scope when variables work in one context but not another.

Related Articles

Ansible default() Filter GuideAnsible Variable Precedence GuideAnsible register: Save Task OutputAnsible Jinja2 Filters Complete Reference

See also

Fix Ansible "undefined variable" Error: Variable Scope and Precedence

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home