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 Guide • Ansible Variable Precedence Guide • Ansible register: Save Task Output • Ansible Jinja2 Filters Complete ReferenceSee also
• Fix Ansible "undefined variable" Error: Variable Scope and PrecedenceCategory: installation