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 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.

Ansible Fix 'VARIABLE IS NOT DEFINED' Error: Undefined Variables

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 the ansible_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 $

code with ❤️ in GitHub

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:

  1. User-defined variables — you create them with vars:, set_fact, register, etc.
  2. 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

VariableExample ValueDescription
ansible_hostnamewebserver01Short hostname
ansible_fqdnwebserver01.example.comFully qualified domain name
ansible_os_familyRedHat or DebianOS family
ansible_distributionUbuntuLinux distribution
ansible_distribution_version22.04Distro version
ansible_default_ipv4.address192.168.1.100Primary IPv4 address
ansible_memtotal_mb4096Total RAM in MB
ansible_processor_vcpus4Number of vCPUs

Variables That DON'T Require gather_facts

These are always available:

VariableDescription
inventory_hostnameHostname as defined in inventory
inventory_hostname_shortShort version of inventory hostname
group_namesGroups the current host belongs to
groupsAll groups and their hosts
hostvarsVariables for all hosts
ansible_play_hostsAll 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

VariableFix
ansible_hostnameAdd gather_facts: true
ansible_os_familyFacts must be gathered
ansible_default_ipv4Facts + network available
itemMust be inside a loop
Custom varDefine 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.

Category: troubleshooting

Watch the video: Ansible Fix 'VARIABLE IS NOT DEFINED' Error: Undefined Variables — Video Tutorial

Browse all Ansible tutorials · AnsiblePilot Home