Ansible Fix Undefined Variable Error: Complete Troubleshooting Guide
By Luca Berton · Published 2024-01-01 · Category: installation
Fix Ansible undefined variable errors. Use default filter, debug variable scope, handle missing facts, and prevent undefined variable issues in playbooks.

Introduction
Today we're going to talk about Ansible troubleshooting, specifically about the undefined variable errors. I'm Luca Berton and welcome to today's episode of Ansible Pilot.See also: Ansible Fix 'VARIABLE IS NOT DEFINED' Error: Undefined Variables
Playbook
The best way of talking about Ansible troubleshooting is to jump in a live Playbook to show you practically the undefined variable error and how to solve it!error code
• underfinedvariable_error.yml---
- name: debug module Playbook
hosts: all
tasks:
- name: debug message
ansible.builtin.debug:
msg: "{{ fruit }}"
error execution
$ ansible-playbook troubleshooting/undefinedvariable_error.yml
PLAY [file module demo] ***************************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [demo.example.com]
TASK [debug message] ******************************************************************************
fatal: [demo.example.com]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'fruit' is undefined\n\nThe error appears to be in '/Users/lberton/prj/github/ansible-pilot/troubleshooting/undefinedvariable_error.yml': line 5, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: debug message\n ^ here\n"}
PLAY RECAP ****************************************************************************************
demo.example.com : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
fix code
• underfinedvariable_fix.yml---
- name: debug module Playbook
hosts: all
vars:
fruit: "apple"
tasks:
- name: debug message
ansible.builtin.debug:
msg: "{{ fruit }}"
fix execution
$ ansible-playbook troubleshooting/undefinedvariable_fix.yml
PLAY [debug module Playbook] **************************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [demo.example.com]
TASK [debug message] ******************************************************************************
ok: [demo.example.com] => {
"msg": "apple"
}
PLAY RECAP ****************************************************************************************
demo.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Conclusion
Now you know how to troubleshoot the most common Ansible undefined variable error.See also: Ansible troubleshooting - AWS Failed to import the required Python library (botocore or boto3)
Why Variables Become Undefined
Typo in variable name — most common cause Variable defined in wrong scope — defined in one play/role but used in another Missingvars: section — forgot to define the variable
Conditional variable — variable only exists when a previous task ran
Missing role defaults — expected defaults/main.yml to provide a value
Fix Strategies
Strategy 1: Define the variable
---
- name: Define variables properly
hosts: all
vars:
fruit: "apple"
color: "red"
tasks:
- name: use the variable
ansible.builtin.debug:
msg: "The {{ fruit }} is {{ color }}"
Strategy 2: Use the default() filter
The safest approach for optional variables:
- name: Safe variable usage
ansible.builtin.debug:
msg: "Value is {{ my_var | default('fallback_value') }}"
# Skip task if variable is undefined
- name: Only run if variable exists
ansible.builtin.debug:
msg: "{{ optional_var }}"
when: optional_var is defined
Strategy 3: Use vars_prompt for interactive input
---
- name: Ask for input
hosts: all
vars_prompt:
- name: target_package
prompt: "Which package to install?"
default: "curl"
tasks:
- name: Install the package
ansible.builtin.package:
name: "{{ target_package }}"
state: present
become: true
Strategy 4: Role defaults
In roles, define default values in defaults/main.yml:
# roles/webserver/defaults/main.yml
http_port: 80
server_name: localhost
document_root: /var/www/html
These can be overridden by any other variable source but prevent "undefined" errors.
Strategy 5: Register variables from task output
- name: Get disk usage
ansible.builtin.command: df -h /
register: disk_result
- name: Show disk usage
ansible.builtin.debug:
msg: "{{ disk_result.stdout }}"
⚠️ Warning: register variables only exist for hosts where the task ran. If a task is skipped (via when), the registered variable won't exist on that host.
See also: Ansible Vault Error: Fix 'Attempting to Decrypt but No Vault Secrets Found'
Variable Precedence (simplified)
From lowest to highest priority:
Role defaults (defaults/main.yml)
Inventory group_vars
Inventory host_vars
Playbook vars:
Role vars (vars/main.yml)
Task vars:
Extra vars (-e / --extra-vars)
If a variable is "undefined", check if it should be coming from one of these sources.
Debugging Undefined Variables
# Check what variables are available for a host
ansible hostname -m debug -a "var=hostvars[inventory_hostname]" -i inventory
# Check a specific variable
ansible hostname -m debug -a "var=my_variable" -e @vars.yml -i inventory
# Run with verbose output to see variable resolution
ansible-playbook playbook.yml -vvv
FAQ
How do I make a variable optional?
Use the default() Jinja2 filter:
msg: "{{ optional_var | default(omit) }}" # Omits the parameter entirely
msg: "{{ optional_var | default('') }}" # Uses empty string
msg: "{{ optional_var | default([]) }}" # Uses empty list
How do I fail gracefully if a required variable is missing?
- name: Validate required variables
ansible.builtin.assert:
that:
- database_host is defined
- database_host | length > 0
fail_msg: "database_host must be defined!"
Why does my variable work in one play but not another?
Variables set with set_fact or register are host-scoped — they persist across plays for the same host. But vars: defined at play level only exist within that play.
The Error
fatal: [host]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'my_var' is undefined"}
Quick Fixes
Use default Filter
# Provide fallback value
- debug: msg="{{ my_var | default('not set') }}"
# Default to empty list
- debug: msg="{{ my_list | default([]) }}"
# Default to empty dict
- debug: msg="{{ my_dict | default({}) }}"
# Chain defaults
- debug: msg="{{ primary_var | default(secondary_var) | default('fallback') }}"
Check if Defined
- debug: msg="{{ my_var }}"
when: my_var is defined
# Skip entire block
- block:
- template: src=app.conf.j2 dest=/etc/app.conf
- service: name=myapp state=restarted
when: app_config is defined
Assert Required Variables
- assert:
that:
- db_host is defined
- db_host | length > 0
- db_password is defined
fail_msg: "Required database variables not set!"
Common Causes
Variable Not in Scope
# Variables set in one play don't carry to another
- hosts: webservers
tasks:
- set_fact: app_version=2.0
- hosts: dbservers
tasks:
- debug: msg="{{ app_version }}" # UNDEFINED!
# Fix: use hostvars
- debug: msg="{{ hostvars[groups['webservers'][0]].app_version }}"
Role Variables Not Available
# Role defaults only available within the role
# roles/myapp/defaults/main.yml
# app_port: 8080
# In playbook — can't access directly
# Fix: Pass as role variable
- hosts: all
roles:
- role: myapp
vars:
app_port: 9090
Registered Variable Conditional
# Variable only exists if task ran
- command: check-something
register: result
when: run_check | default(true)
# This fails if task was skipped!
- debug: msg="{{ result.stdout }}"
# Fix: Check if defined
- debug: msg="{{ result.stdout }}"
when: result is defined and result is not skipped
Loop Variable Outside Loop
# WRONG - item only exists inside loop
- debug: msg="{{ item }}"
loop: [a, b, c]
- debug: msg="{{ item }}" # UNDEFINED!
Variable Precedence (Highest to Lowest)
Extra vars (-e)
Task vars
Block vars
Role vars / include_vars
Play vars
Host facts / set_fact
Inventory host_vars
Inventory group_vars
Role defaults
Debugging
# Verbose shows variable resolution
ansible-playbook site.yml -vvv
# Print all variables for a host
ansible web1 -m debug -a "var=hostvars[inventory_hostname]"
# In playbook
- debug: var=vars # All variables in scope
- debug: var=hostvars[inventory_hostname]
FAQ
Why is my group_vars variable undefined?
Check: file is in correct path (group_vars/), host belongs to that group, and YAML syntax is valid.
How do I make a variable truly required?
- fail: msg="db_host must be set"
when: db_host is not defined or db_host == ""
Can I set a global default for undefined variables?
# ansible.cfg — NOT recommended (hides bugs)
[defaults]
# error_on_undefined_vars = false # Don't do this!
The Error
fatal: [web1]: FAILED! => {"msg": "'my_variable' is undefined"}
Quick Fixes
# Use default filter
msg: "{{ my_var | default('fallback') }}"
# Check if defined
when: my_var is defined
# Require it (clear error message)
msg: "{{ my_var | mandatory }}"
Common Causes
1. Variable Never Set
# FIX: Define in vars, group_vars, or defaults
- hosts: all
vars:
my_var: "value"
2. Typo in Variable Name
vars:
database_host: db.example.com
# WRONG
msg: "{{ databse_host }}" # Typo!
# CORRECT
msg: "{{ database_host }}"
3. Scope Issue (set_fact)
# set_fact only sets for current host
- set_fact: my_var="hello"
when: inventory_hostname == "web1"
# web2 doesn't have my_var!
# FIX: use default
- debug: msg="{{ my_var | default('not set') }}"
4. Facts Not Gathered
# gather_facts: false means no ansible_* facts
- hosts: all
gather_facts: true # Enable this
5. register from Skipped Task
- command: echo "hello"
register: result
when: false # Task skipped!
# result is defined but has .skipped = true
- debug: msg="{{ result.stdout | default('skipped') }}"
Safe Variable Patterns
# Nested access with default
"{{ config.database.host | default('localhost') }}"
# Check nested key exists
when: config is defined and config.database is defined
# Default for entire dict
"{{ (config | default({})).database | default({}).host | default('localhost') }}"
# Simpler with ternary
"{{ config.database.host if config.database is defined else 'localhost' }}"
Debug Variables
# What type is it?
- debug: msg="{{ my_var | type_debug }}"
# Is it defined?
- debug: msg="{{ my_var is defined }}"
# Print all variables
- debug: var=hostvars[inventory_hostname]
Role Defaults
# roles/myrole/defaults/main.yml
# Always define defaults — prevents undefined errors
app_port: 8080
app_debug: false
app_log_level: info
FAQ
default('') vs default(omit)?
default('') gives empty string. default(omit) removes the parameter entirely (as if not specified).
How to make a variable required?
msg: "{{ required_var | mandatory }}"
# Gives clear error: "Mandatory variable 'required_var' not defined"
Undefined in Jinja2 template?
Same fixes — use {{ var | default('') }} or {% if var is defined %} blocks in templates.
Related Articles
• Ansible role best practicesCategory: installation
Watch the video: Ansible Fix Undefined Variable Error: Complete Troubleshooting Guide — Video Tutorial