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: 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 reference

Category: troubleshooting

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

Browse all Ansible tutorials · AnsiblePilot Home