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:
$ 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:
$ 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:
$ ansible-playbook configure.yml
TASK [Template configuration file] *********************************************
fatal: [db-server-01]: FAILED! => {
"msg": "AnsibleUndefinedVariable: 'db_password' is undefined"
}Hostvars undefined for another host:
$ 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:
$ 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-03Common 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:
- debug:
msg: "{{ app.config.port }}"
# Fails if app.config doesn't exist4. 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:
- set_fact:
ssl_cert_path: /etc/ssl/cert.pem
when: enable_ssl | default(false)
# ssl_cert_path undefined when enable_ssl is false6. Typo in Variable Name
Simple naming error:
- debug:
msg: "{{ db_hots }}" # Typo: should be db_hostStep-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:
# Run for specific host
ansible-playbook site.yml --limit web-server-03 -vvvStep 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:
- 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:
# 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:
# 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
doneVerification
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" ```
Related Issues
- [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)
Related Articles
- [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>