Ansible Fix 'VARIABLE IS NOT DEFINED' Error: Undefined Variables
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
Fix Ansible VARIABLE IS NOT DEFINED error. Troubleshoot undefined variables, missing facts, gather_facts issues, and use default filter for safe access.

Introduction
Today we’re going to talk about Ansible troubleshooting, specifically about VARIABLE IS NOT DEFINED! Message. Most of the time the root cause is a misspelled variable or a variable really not defined. This use case is special about theansible_hostname internal variable.
I’m Luca Berton and welcome to today’s episode of Ansible Pilot.
See also: Ansible Fix Undefined Variable Error: Complete Troubleshooting Guide
Playbook
The best way of talking about Ansible troubleshooting is to jump in a live Playbook to show you practically the VARIABLE IS NOT DEFINED! and how to solve it!
error code
---
- name: hostname Playbook
hosts: all
gather_facts: false
tasks:
- name: print hostname
ansible.builtin.debug:
var: ansible_hostname
See also: Ansible troubleshooting - AWS Failed to import the required Python library (botocore or boto3)
error execution
ansible-pilot $ ansible-playbook -i virtualmachines/demo/inventory troubleshooting/variablenotdefined_error.yml
PLAY [hostname Playbook] ******************************************************************************
TASK [print hostname] *****************************************************************************
ok: [demo.example.com] => {
"ansible_hostname": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP ****************************************************************************************
demo.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
fix code
---
- name: hostname Playbook
hosts: all
gather_facts: true
tasks:
- name: print hostname
ansible.builtin.debug:
var: ansible_hostname
See also: Ansible Vault Error: Fix 'Attempting to Decrypt but No Vault Secrets Found'
fix execution
ansible-pilot $ ansible-playbook -i virtualmachines/demo/inventory troubleshooting/variablenotdefined_fix.yml
PLAY [hostname Playbook] ******************************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [demo.example.com]
TASK [print hostname] *****************************************************************************
ok: [demo.example.com] => {
"ansible_hostname": "Playbook"
}
PLAY RECAP ****************************************************************************************
demo.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
Conclusion
Now you know better how to troubleshoot the Ansible VARIABLE IS NOT DEFINED! message.Why Does This Error Happen?
Ansible has two types of variables:
User-defined variables — you create them with vars:, set_fact, register, etc.
Facts (built-in variables) — Ansible collects them automatically from managed hosts
Facts like ansible_hostname, ansible_os_family, ansible_distribution, and ansible_default_ipv4 are only available if gather_facts: true (which is the default). If you set gather_facts: false, these variables don't exist.
Common Variables That Require gather_facts
| Variable | Example Value | Description |
|----------|---------------|-------------|
| ansible_hostname | webserver01 | Short hostname |
| ansible_fqdn | webserver01.example.com | Fully qualified domain name |
| ansible_os_family | RedHat or Debian | OS family |
| ansible_distribution | Ubuntu | Linux distribution |
| ansible_distribution_version | 22.04 | Distro version |
| ansible_default_ipv4.address | 192.168.1.100 | Primary IPv4 address |
| ansible_memtotal_mb | 4096 | Total RAM in MB |
| ansible_processor_vcpus | 4 | Number of vCPUs |
Variables That DON'T Require gather_facts
These are always available:
| Variable | Description |
|----------|-------------|
| inventory_hostname | Hostname as defined in inventory |
| inventory_hostname_short | Short version of inventory hostname |
| group_names | Groups the current host belongs to |
| groups | All groups and their hosts |
| hostvars | Variables for all hosts |
| ansible_play_hosts | All hosts in the current play |
Fix: Enable gather_facts
# ✅ CORRECT — gather_facts defaults to true
---
- name: hostname Playbook
hosts: all
tasks:
- name: print hostname
ansible.builtin.debug:
var: ansible_hostname
# ✅ CORRECT — explicit gather_facts: true
---
- name: hostname Playbook
hosts: all
gather_facts: true
tasks:
- name: print hostname
ansible.builtin.debug:
var: ansible_hostname
Fix: Use inventory_hostname Instead
If you disabled gather_facts for performance and just need the hostname:
---
- name: hostname Playbook
hosts: all
gather_facts: false
tasks:
- name: print hostname (from inventory)
ansible.builtin.debug:
var: inventory_hostname
Fix: Gather Facts Manually When Needed
---
- name: Selective facts gathering
hosts: all
gather_facts: false
tasks:
- name: do fast tasks first
ansible.builtin.command: echo "no facts needed"
- name: now gather facts when needed
ansible.builtin.setup:
gather_subset:
- network
- hardware
- name: now ansible_hostname is available
ansible.builtin.debug:
var: ansible_hostname
Performance: gather_subset
Full fact gathering can take 5-10 seconds per host. Use gather_subset to speed it up:
---
- name: Fast facts
hosts: all
gather_facts: true
gather_subset:
- '!all' # Exclude everything
- network # Only collect network facts
tasks:
- name: show IP
ansible.builtin.debug:
var: ansible_default_ipv4.address
Available subsets: all, min, hardware, network, virtual, ohai, facter.
FAQ
What's the difference between ansible_hostname and inventory_hostname?
•ansible_hostname: The actual hostname of the remote machine (from hostname command). Requires gather_facts: true.
• inventory_hostname: The name you gave the host in your inventory file. Always available.
These can be different! If your inventory says webserver but the machine's hostname is ip-172-31-0-5, they won't match.
How do I check all available facts?
ansible hostname -m setup
# Or filter specific facts
ansible hostname -m setup -a 'filter=ansible_os*'
Why is gather_facts slow?
Ansible runs the setup module which collects hundreds of facts about the system. For large inventories, use gather_subset or gather_facts: false with manual setup calls where needed.
Common Causes
Facts not gathered
# WRONG - facts disabled but using fact variable
- hosts: all
gather_facts: false
tasks:
- debug: msg="{{ ansible_hostname }}"
# ERROR: 'ansible_hostname' is undefined
# FIX - enable fact gathering
- hosts: all
gather_facts: true
tasks:
- debug: msg="{{ ansible_hostname }}"
Explicit gather when needed
- hosts: all
gather_facts: false
tasks:
- name: Do quick task first
command: uptime
- name: Now gather facts
ansible.builtin.setup:
- debug: msg="{{ ansible_hostname }}"
Variable not defined in scope
# WRONG - var defined in one play, used in another
- hosts: webservers
tasks:
- set_fact: my_var=hello
- hosts: dbservers
tasks:
- debug: msg="{{ my_var }}" # UNDEFINED!
# FIX - use hostvars
- debug: msg="{{ hostvars[groups['webservers'][0]].my_var }}"
Use default Filter
# Prevent undefined errors with defaults
- debug:
msg: "{{ my_var | default('not set') }}"
- debug:
msg: "{{ ansible_hostname | default(inventory_hostname) }}"
# Nested access
- debug:
msg: "{{ result.stdout | default('no output') }}"
# With omit (skip parameter entirely)
- user:
name: deploy
shell: "{{ custom_shell | default(omit) }}"
Check if Defined
- debug: msg="{{ my_var }}"
when: my_var is defined
- fail: msg="Required var missing!"
when: required_var is not defined
# Assert multiple vars exist
- assert:
that:
- db_host is defined
- db_password is defined
- app_version is defined
fail_msg: "Missing required variables"
Common Undefined Variables
| Variable | Fix |
|----------|-----|
| ansible_hostname | Add gather_facts: true |
| ansible_os_family | Facts must be gathered |
| ansible_default_ipv4 | Facts + network available |
| item | Must be inside a loop |
| Custom var | Define in vars, group_vars, or host_vars |
Debugging
# Print all available variables
- debug: var=hostvars[inventory_hostname]
# Check specific variable type
- debug:
msg: "{{ my_var | type_debug }}"
when: my_var is defined
# List all facts
- setup:
register: all_facts
- debug: var=all_facts.ansible_facts
# Verbose mode shows variable resolution
ansible-playbook site.yml -vvv
Role Variables
# roles/myapp/defaults/main.yml (lowest priority)
app_port: 8080
# roles/myapp/vars/main.yml (higher priority)
app_name: myapp
# roles/myapp/tasks/main.yml
- debug: msg="{{ app_name }} on port {{ app_port }}"
FAQ
Why is ansible_hostname undefined?
gather_facts: false was set. Either set it to true or manually run the setup module before using facts.
How do I make a variable required?
- assert:
that: my_var is defined and my_var | length > 0
fail_msg: "my_var must be set"
Variable defined in another play?
Variables from one play aren't automatically available in another. Use set_fact (persists in hostvars) or pass via group_vars.
The Error
fatal: [web1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible_hostname' is undefined."}
Common Causes and Fixes
1. Facts Not Gathered
# WRONG — facts disabled
- hosts: all
gather_facts: false
tasks:
- debug: msg="{{ ansible_hostname }}" # FAILS!
# CORRECT — enable fact gathering
- hosts: all
gather_facts: true # Default
tasks:
- debug: msg="{{ ansible_hostname }}"
2. Variable Not Defined
# WRONG — my_var never set
- debug: msg="{{ my_var }}"
# CORRECT — use default filter
- debug: msg="{{ my_var | default('fallback') }}"
# CORRECT — check first
- debug: msg="{{ my_var }}"
when: my_var is defined
3. Variable Typo
vars:
app_port: 8080
# WRONG — typo!
- debug: msg="{{ app_prot }}"
# CORRECT
- debug: msg="{{ app_port }}"
4. Variable Scope
# set_fact only applies to current host
- set_fact:
my_var: "hello"
when: inventory_hostname == "web1"
# FAILS on web2 — my_var not defined there
- debug: msg="{{ my_var }}"
# FIX — use default or hostvars
- debug: msg="{{ hostvars['web1']['my_var'] | default('not set') }}"
Safe Variable Access
# default filter
"{{ my_var | default('fallback_value') }}"
"{{ my_dict.key | default('missing') }}"
"{{ my_list[0] | default('empty') }}"
# Nested default
"{{ config.database.host | default('localhost') }}"
# Mandatory (explicit error)
"{{ required_var | mandatory }}"
Debug Variables
# Check if defined
- debug: msg="{{ 'defined' if my_var is defined else 'undefined' }}"
# Print all variables
- debug: var=vars
# Print all facts
- setup:
register: facts
- debug: var=facts.ansible_facts.ansible_hostname
Gather Specific Facts
# Gather only what you need (faster)
- setup:
gather_subset:
- network
- hardware
# Or gather all facts explicitly
- setup:
gather_subset: all
FAQ
Why is ansible_hostname undefined?
Most common: gather_facts: false in the play. Either enable it or run setup module manually.
How to set a default for all undefined variables?
# ansible.cfg — NOT recommended (masks real errors)
[defaults]
# error_on_undefined_vars = False
Variable defined in one play, undefined in next?
Variables from set_fact persist per host across plays. But vars: and register: are play-scoped. Use set_fact for cross-play variables.
Related Articles
• Ansible inventory complete referenceCategory: troubleshooting
Watch the video: Ansible Fix 'VARIABLE IS NOT DEFINED' Error: Undefined Variables — Video Tutorial