Introduction

When Ansible evaluates Jinja2 expressions in playbooks or templates, any reference to an undefined variable causes an immediate failure. The AnsibleUndefinedVariable error stops playbook execution before any tasks can run, blocking deployments until the missing variable is identified and resolved. This issue commonly occurs when variables are defined for some hosts but not others, when inventory changes remove previously available variables, or when templates assume data structures that don't exist.

Understanding Ansible's variable precedence and scope is essential for diagnosing why a variable is undefined in a specific context.

Symptoms

Basic undefined variable error:

bash
$ ansible-playbook deploy.yml
TASK [Render application config] ************************************************
fatal: [web-server-01]: FAILED! => {
    "msg": "The task includes an option with an undefined variable. The error was: 'app_port' is undefined\n\nThe error appears to be in '/home/user/roles/app/tasks/main.yml': line 15, column 3"
}

Undefined nested dictionary key:

bash
$ ansible-playbook site.yml
TASK [Configure nginx] *********************************************************
fatal: [web-server-02]: FAILED! => {
    "msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'ssl_cert'"
}

Undefined in template:

bash
$ ansible-playbook configure.yml
TASK [Template configuration file] *********************************************
fatal: [db-server-01]: FAILED! => {
    "msg": "AnsibleUndefinedVariable: 'db_password' is undefined"
}

Hostvars undefined for another host:

bash
$ ansible-playbook cluster.yml
fatal: [node-01]: FAILED! => {
    "msg": "AnsibleUndefinedVariable: \"hostvars['node-05']\" is undefined. \"hostvars['node-05']\" is undefined"
}

Working for some hosts but not others:

bash
$ ansible-playbook deploy.yml
TASK [Deploy application] *******************************************************
ok: [web-server-01]
ok: [web-server-02]
fatal: [web-server-03]: FAILED! => {"msg": "'tls_enabled' is undefined"}
# Variable defined for web-server-01 and web-server-02 but not web-server-03

Common Causes

1. Variable Defined in Specific Group Vars

Variable exists for one group but not another:

```bash $ cat group_vars/production.yml db_host: prod-db.internal

$ cat group_vars/staging.yml # db_host not defined here! ```

2. Missing Host Variable

Variable defined per-host but missing for some:

```bash $ cat host_vars/web-server-01.yml app_port: 8080

$ ls host_vars/web-server-02.yml # File doesn't exist ```

3. Nested Dictionary Without Parent Check

Accessing nested key without checking parent:

yaml
- debug:
    msg: "{{ app.config.port }}"
# Fails if app.config doesn't exist

4. Variable Precedence Issues

Variable defined at wrong precedence level:

```yaml # In role defaults (lowest precedence) db_port: 5432

# But host_vars override and set to undefined # host_vars/server.yml: # db_port: !null ```

5. Conditional Variable Definition

Variable only defined conditionally:

yaml
- set_fact:
    ssl_cert_path: /etc/ssl/cert.pem
  when: enable_ssl | default(false)
# ssl_cert_path undefined when enable_ssl is false

6. Typo in Variable Name

Simple naming error:

yaml
- debug:
    msg: "{{ db_hots }}"  # Typo: should be db_host

Step-by-Step Fix

Step 1: Identify the Exact Missing Variable

Run with verbose output to see the full error context:

```bash # Run with high verbosity ansible-playbook site.yml -vvv

# Look for lines like: # "msg": "'app_port' is undefined" # The error appears to be in 'roles/app/tasks/main.yml': line 15 ```

Check which host is failing:

bash
# Run for specific host
ansible-playbook site.yml --limit web-server-03 -vvv

Step 2: Debug Variable State

Add debug tasks to inspect variable state:

```yaml - name: Debug all variables for this host debug: var: hostvars[inventory_hostname]

  • name: Check specific variable
  • debug:
  • msg: "app_port is {{ app_port | default('UNDEFINED') }}"
  • name: Check if variable is defined
  • debug:
  • msg: "app_port is {{ 'defined' if app_port is defined else 'UNDEFINED' }}"
  • name: Show variable sources
  • debug:
  • msg: "app_port comes from {{ lookup('vars', 'app_port', default='nowhere') }}"
  • `

Use ansible ad-hoc for quick checks:

```bash # Check if variable exists for host ansible web-server-03 -m debug -a "msg={{ app_port | default('UNDEFINED') }}"

# List all variables ansible web-server-03 -m debug -a "var=hostvars[inventory_hostname]"

# Check group membership ansible web-server-03 -m debug -a "msg={{ group_names }}" ```

Step 3: Trace Variable Scope

Understand where the variable should come from:

```yaml # Check inventory file location - debug: msg: | Inventory directory: {{ inventory_dir }} Inventory file: {{ inventory_file }} Group names: {{ group_names | join(', ') }}

# Check role defaults - debug: msg: "Role defaults loaded from: {{ role_path }}/defaults/main.yml" ```

Check variable precedence:

```bash # View all variables with their sources ansible-inventory --list | jq '._meta.hostvars["web-server-03"]'

# Check group vars cat group_vars/*.yml | grep -A5 app_port

# Check host vars cat host_vars/web-server-03.yml ```

Step 4: Add Safe Defaults for Optional Variables

Use Jinja2 default filter for optional values:

```yaml # In tasks - name: Configure port set_fact: listen_port: "{{ app_port | default(8080) }}"

# In templates server { listen {{ app_port | default(8080) }}; } ```

Use default(omit) to skip undefined optional parameters:

yaml
- name: Create user
  user:
    name: "{{ username }}"
    shell: "{{ user_shell | default(omit) }}"
    groups: "{{ user_groups | default(omit) }}"

Step 5: Handle Nested Dictionary Access Safely

Check for parent existence before accessing child:

```yaml # WRONG - fails if app.config doesn't exist - debug: msg: "{{ app.config.port }}"

# CORRECT - safe access with default - debug: msg: "{{ (app.config | default({})).port | default(8080) }}"

# Or use a helper - set_fact: app_config: "{{ app.config | default({}) }}" - debug: msg: "{{ app_config.port | default(8080) }}" ```

Create a safe dictionary access filter:

yaml
# In ansible.cfg or playbook
- name: Safe nested access
  debug:
    msg: "{{ app | json_query('config.port') | default(8080) }}"

Step 6: Validate Required Variables Early

Add explicit validation for required variables:

```yaml - name: Validate required variables assert: that: - db_host is defined - db_port is defined - db_user is defined - db_password is defined fail_msg: | Required database variables are not defined. Please define them in group_vars or host_vars. success_msg: "All required variables are defined" run_once: true

  • name: Validate variable types
  • assert:
  • that:
  • - db_port is number
  • - db_host is string
  • fail_msg: "Variable types are incorrect"
  • `

Step 7: Handle Conditional Variables Properly

Initialize variables even when condition is false:

```yaml # WRONG - ssl_cert_path undefined when enable_ssl is false - set_fact: ssl_cert_path: /etc/ssl/cert.pem when: enable_ssl | default(false)

# CORRECT - always set, with conditional value - set_fact: ssl_cert_path: "{{ '/etc/ssl/cert.pem' if enable_ssl | default(false) else '' }}"

# Then check safely - debug: msg: "SSL cert: {{ ssl_cert_path }}" when: ssl_cert_path | default('') ```

Step 8: Fix Inventory Structure

Ensure variables are defined in the right places:

```yaml # group_vars/all.yml - variables for all hosts app_name: myapp app_version: "1.0.0"

# group_vars/production.yml - production-specific db_host: prod-db.internal db_port: 5432

# group_vars/staging.yml - staging-specific db_host: staging-db.internal db_port: 5432

# host_vars/web-server-03.yml - host-specific overrides app_port: 9000 ```

Create missing variable files:

bash
# Check which hosts are missing variables
for host in $(ansible all --list-hosts | sed 's/^[ \t]*//'); do
  echo "=== $host ==="
  ansible $host -m debug -a "msg={{ app_port | default('UNDEFINED') }}" 2>/dev/null
done

Verification

Test playbook with verbose output:

```bash # Run playbook for all hosts ansible-playbook site.yml -v

# Verify no undefined variable errors # All tasks should show ok, changed, or skipped - not failed

# Test specific host that was failing ansible-playbook site.yml --limit web-server-03 -vv ```

Verify variable state:

```bash # Check variable exists for all hosts ansible all -m debug -a "msg={{ app_port | default('UNDEFINED') }}"

# Check nested variables ansible all -m debug -a "msg={{ (app.config | default({})).port | default('UNDEFINED') }}"

# Verify validation passes ansible-playbook validate_vars.yml ```

Run template tasks specifically:

```bash # Test template rendering ansible-playbook site.yml --tags template -v

# Check rendered files on targets ansible all -m shell -a "cat /etc/app/config.yml" ```

  • [ansible-template-jinja2-variable-undefined](/articles/ansible-template-jinja2-variable-undefined)
  • [ansible-variable-precedence-issues](/articles/ansible-variable-precedence-issues)
  • [ansible-inventory-variable-scoping](/articles/ansible-inventory-variable-scoping)
  • [WordPress troubleshooting: Ansible Artifact Download Uses an Old Mi](ansible-artifact-download-uses-an-old-mirror-after-proxy-change)
  • [WordPress troubleshooting: Ansible Audit Trail Misses Events Under ](ansible-audit-trail-misses-events-under-burst-load)
  • [WordPress troubleshooting: Ansible Background Worker Gets Stuck in ](ansible-background-worker-stuck-in-a-retry-loop)
  • [WordPress troubleshooting: Ansible Backup Completes but Restore Fai](ansible-backup-completes-but-restore-fails-checksum-validation)
  • [WordPress troubleshooting: Ansible Batch Importer Duplicates Rows A](ansible-batch-importer-duplicates-rows-after-a-retry)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "WordPress troubleshooting: Ansible Playbook Failed Because a Jinja2", "description": "Learn how to fix Ansible Playbook Failed Because a Jinja2 Variable Was Undefined. Professional WordPress troubleshooting solutions with step-by-step guidance. WP error fix, WordPress optimization, WP security, WordPress performance.", "url": "https://www.fixwikihub.com/ansible-playbook-undefined-variable-jinja2-strict-mode", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-22T14:36:33.487Z", "dateModified": "2026-01-22T14:36:33.487Z" } </script>