Fix AnsibleUndefinedVariable: 'variable_name' is undefined

beginner๐Ÿ”ง Ansible2026-03-18| Ansible 2.9+, any control node (Linux/macOS), Jinja2 templating

Error Message

AnsibleUndefinedVariable: 'variable_name' is undefined
#ansible#variable#jinja2#template

The Error

Your playbook stops dead with:

fatal: [host]: FAILED! => {
    "msg": "The task includes an option with an undefined variable. The error was: 'variable_name' is undefined"
}
AnsibleUndefinedVariable: 'variable_name' is undefined

Ansible hit a Jinja2 template that referenced a variable it couldn't find. Maybe it was never defined. Maybe it's defined in the wrong scope. Or there's a typo in the name.

Why This Happens

  • The variable isn't defined anywhere โ€” no vars:, no defaults/main.yml, no inventory, no -e flag
  • Wrong scope: defined inside a block or in host-specific vars that don't apply to this task
  • A typo (db_port vs db_ports โ€” easy to miss)
  • A nested key doesn't exist (result.output when result has no output key)
  • A registered variable used before its task runs, or when that task was skipped

Step-by-Step Fix

Step 1 โ€” Find where the variable should be defined

Start with -v to get more detail in the error output:

ansible-playbook site.yml -v

Then hunt for the variable across your project:

grep -r "variable_name" roles/ group_vars/ host_vars/ inventory/

No results? That's your answer โ€” it's not defined anywhere.

Step 2 โ€” Define the variable in the right place

Pick the location that matches how broadly the variable should apply:

In the playbook directly (task-level scope):

- name: Deploy app
  hosts: webservers
  vars:
    app_port: 8080
    app_user: deploy
  tasks:
    - name: Start app
      ansible.builtin.systemd:
        name: myapp
      environment:
        PORT: "{{ app_port }}"

In group_vars (applies to every host in that group):

# group_vars/webservers.yml
app_port: 8080
app_user: deploy

In role defaults (lowest priority โ€” easily overridden by anything else):

# roles/myapp/defaults/main.yml
app_port: 8080
app_user: deploy

At runtime with -e (highest priority โ€” overrides everything):

ansible-playbook site.yml -e "app_port=8080 app_user=deploy"

Step 3 โ€” Handle optional variables with defaults

Not every variable needs to be required. Use Jinja2's default() filter to supply a fallback value instead of letting the playbook crash:

- name: Configure timeout
  ansible.builtin.template:
    src: config.j2
    dest: /etc/myapp/config.conf
  vars:
    timeout: "{{ connection_timeout | default(30) }}"
    debug_mode: "{{ enable_debug | default(false) }}"

Need to skip the parameter entirely when it's not set? Use default(omit):

- name: Create user
  ansible.builtin.user:
    name: "{{ username }}"
    password: "{{ user_password | default(omit) }}"

Step 4 โ€” Fix nested variable access

Accessing a key inside a dict? That key might not exist. Guard it:

# Unsafe โ€” fails if result has no stdout key
- debug:
    msg: "{{ result.stdout }}"

# Safe โ€” returns empty string if stdout is missing
- debug:
    msg: "{{ result.stdout | default('') }}"

# Or check first
- debug:
    msg: "{{ result.stdout }}"
  when: result.stdout is defined

Step 5 โ€” Fix registered variables used after skipped tasks

Classic footgun: a task gets skipped, the variable it was supposed to register never exists, then a later task tries to use it.

# Problem: if this task is skipped, check_result is undefined
- name: Check service
  ansible.builtin.command: systemctl status myapp
  register: check_result
  when: run_checks | default(false)

# Fix: guard the usage
- name: Show result
  debug:
    msg: "{{ check_result.stdout }}"
  when: check_result is defined and check_result.stdout is defined

Verify the Fix

Re-run the playbook. The task should pass instead of hitting FAILED. Before running for real, confirm Ansible can actually see your variable:

# Check a specific variable for all hosts in a group
ansible -m debug -a "var=app_port" webservers

# Dump all variables for one specific host
ansible -m debug -a "var=hostvars[inventory_hostname]" webservers --limit web01

A dry-run catches issues without making any changes:

ansible-playbook site.yml --check --diff

Quick Tips

  • Variable precedence: Ansible has 22 levels of precedence. Getting an unexpected value? Check the precedence order. Extra vars (-e) always win; role defaults always lose.
  • Strict mode: Set error_on_undefined_vars = True in ansible.cfg to catch undefined variables earlier โ€” before they silently produce wrong output instead of a clean error.
  • Dump all vars mid-play: Add a temporary debug task to see exactly what's available at that point in the play:
  • name: Debug all vars debug: var: vars
  
  - **Bare variables in `when:`**: `when: my_var` blows up if `my_var` is undefined. Write it as `when: my_var is defined and my_var` instead.

Related Error Notes