Ansible Variable Precedence: Complete Order of Priority (2026)
By Luca Berton · Published 2024-01-01 · Category: database-automation
Complete guide to Ansible variable precedence. Understand all 22 precedence levels, from command-line extra vars (highest) to role defaults (lowest).
One of the most confusing aspects of Ansible is variable precedence — when the same variable is defined in multiple places, which value wins? Ansible has 22 levels of variable precedence, and understanding them is critical for debugging unexpected behavior.
The Complete Precedence Order
From lowest (easily overridden) to highest (wins over everything):
Command-line values (-u, -b, etc. — not -e)
Role defaults (roles/x/defaults/main.yml)
Inventory file or script group vars
Inventory group_vars/all
Playbook group_vars/all
Inventory group_vars/
Playbook group_vars/
Inventory file or script host vars
Inventory host_vars/
Playbook host_vars/
Host facts / cached set_facts
Play vars (vars: in a play)
Play vars_prompt
Play vars_files
Role vars (roles/x/vars/main.yml)
Block vars (inside block:)
Task vars (on a specific task)
include_vars
set_facts / registered vars
Role (and include_role) params
include_params
Extra vars (-e / --extra-vars) — ALWAYS WINS
See also: Ansible vars_files: Load Variables from External YAML Files (Guide)
The Rules That Matter
Rule 1: Extra vars (-e) Always Win
ansible-playbook site.yml -e "http_port=9090"
This overrides http_port everywhere — inventory, group_vars, role vars, set_fact, everywhere. Nothing can override extra vars.
Rule 2: Role Defaults Are Meant to Be Overridden
# roles/webserver/defaults/main.yml
http_port: 80 # Lowest precedence — easy to override
max_workers: 4
enable_ssl: false
This is why role defaults exist — they provide sensible defaults that users can override from almost anywhere.
Rule 3: Role Vars Are Hard to Override
# roles/webserver/vars/main.yml
required_packages: # High precedence — NOT meant to be overridden
- nginx
- openssl
roles/x/vars/main.yml has much higher precedence than defaults/main.yml. Use vars for values that should NOT be overridden by users.
Rule 4: set_fact Overrides Almost Everything
- name: Override at runtime
ansible.builtin.set_fact:
http_port: 9090
set_fact is near the top — it overrides play vars, role vars, group_vars, and host_vars. Only extra vars and include params beat it.
Rule 5: group_vars and host_vars Have Two Sources
# Inventory group_vars (lower precedence)
inventory/group_vars/webservers.yml
# Playbook group_vars (higher precedence)
group_vars/webservers.yml
Playbook-adjacent group_vars/ overrides inventory-adjacent group_vars/.
Practical Examples
Example 1: Role Default vs Group Var
# roles/webserver/defaults/main.yml
http_port: 80
# group_vars/production.yml
http_port: 8080
Winner: 8080 — group_vars (level 6-7) beats role defaults (level 2).
Example 2: Group Var vs Play Var
# group_vars/webservers.yml
http_port: 8080
# playbook.yml
- hosts: webservers
vars:
http_port: 9090
Winner: 9090 — play vars (level 12) beats group_vars (level 6-7).
Example 3: Play Var vs Role Var
# playbook.yml
- hosts: webservers
vars:
internal_setting: override_me
roles:
- webserver
# roles/webserver/vars/main.yml
internal_setting: do_not_override
Winner: do_not_override — role vars (level 15) beats play vars (level 12).
Example 4: Role Var vs set_fact
# roles/webserver/vars/main.yml
http_port: 80
# In a task
- ansible.builtin.set_fact:
http_port: 9090
Winner: 9090 — set_fact (level 19) beats role vars (level 15).
Example 5: set_fact vs Extra Vars
# In a task
- ansible.builtin.set_fact:
http_port: 9090
# Command line
# ansible-playbook site.yml -e http_port=3000
Winner: 3000 — extra vars (level 22) beats everything.
See also: Ansible register: Save Task Output to Variables (Complete Guide)
Debugging Variable Precedence
Show Where a Variable Comes From
# Show all variables for a host
ansible webserver1 -m debug -a "var=http_port"
# Show variable with verbose output
ansible-playbook site.yml -vvv
Use debug to Inspect at Runtime
- name: Show variable value and type
ansible.builtin.debug:
msg: |
http_port = {{ http_port }}
type = {{ http_port | type_debug }}
List All Variables
- name: Dump all variables for debugging
ansible.builtin.debug:
var: vars
# Warning: outputs everything, including sensitive data
Best Practices
Use the Right Level for the Right Purpose
| Location | Precedence | Use For |
|----------|-----------|---------|
| roles/x/defaults/ | Lowest | Defaults users should override |
| group_vars/ | Low-medium | Environment-specific config |
| host_vars/ | Medium | Host-specific overrides |
| Play vars: | Medium-high | Play-specific settings |
| roles/x/vars/ | High | Internal role constants |
| set_fact | Very high | Runtime-computed values |
| -e extra vars | Highest | Emergency overrides, CI/CD |
Don't Fight the Precedence System
# Bad: trying to override role vars from group_vars
# roles/webserver/vars/main.yml
http_port: 80
# group_vars/production.yml
http_port: 8080
# This WON'T work — role vars win!
# Good: use role defaults for things you want overridden
# roles/webserver/defaults/main.yml
http_port: 80
# group_vars/production.yml
http_port: 8080
# This works — group_vars beats role defaults
Use Prefixed Variable Names
# Bad: generic name, easy to collide
port: 8080
# Good: role-prefixed, unique
webserver_port: 8080
database_port: 5432
Document Expected Override Points
# roles/webserver/defaults/main.yml
# These variables are intended to be overridden:
# webserver_port: HTTP listen port (default: 80)
# webserver_workers: Number of worker processes (default: 4)
# webserver_ssl_enabled: Enable HTTPS (default: false)
webserver_port: 80
webserver_workers: 4
webserver_ssl_enabled: false
See also: Ansible default() Filter: Set Fallback Values for Undefined Variables
Variable Merging (hash_behaviour)
By default, Ansible replaces dictionaries entirely (it does not merge them):
# group_vars/all.yml
app_config:
port: 8080
workers: 4
# group_vars/production.yml
app_config:
port: 9090
# Result: app_config = {port: 9090}
# 'workers' is GONE — the entire dict was replaced
To merge hashes, use the combine filter:
- name: Merge configs
ansible.builtin.set_fact:
final_config: "{{ default_config | combine(env_config, recursive=True) }}"
> Note: The hash_behaviour=merge setting in ansible.cfg exists but is deprecated and not recommended.
FAQ
What has the highest variable precedence in Ansible?
Extra vars (-e / --extra-vars) have the highest precedence (level 22) and override ALL other variable definitions. Nothing in your playbooks, roles, or inventory can override an extra var.
What is the difference between role defaults and role vars?
Role defaults (roles/x/defaults/main.yml) have the lowest precedence — they're meant to be overridden by users. Role vars (roles/x/vars/main.yml) have high precedence — they're internal values that should NOT be overridden. Use defaults for user-configurable settings and vars for internal constants.
Why is my group_vars variable not working?
Most likely it's being overridden by something with higher precedence: play vars, role vars, set_fact, or extra vars. Use -vvv to see which value is being used and where it comes from.
Does set_fact override group_vars?
Yes, set_fact (level 19) overrides group_vars (levels 4-7), host_vars (levels 9-10), play vars (level 12), and even role vars (level 15). Only extra vars and include params beat set_fact.
How do I see all variable precedence levels?
Run ansible-playbook site.yml -vvv for verbose output showing variable resolution, or check the official docs at docs.ansible.com → "Understanding variable precedence".
Conclusion
The essential rules to remember:
Extra vars (-e) always win — use for CI/CD and emergency overrides
Role defaults = meant to be overridden (lowest)
Role vars = internal constants (high, hard to override)
set_fact = runtime override (very high)
group_vars/host_vars = environment/host config (medium)
When confused, run with -vvv to see what's happening
Design your variable hierarchy intentionally: defaults for user config, vars for internals, group_vars for environments, extra vars for CI/CD.
Related Articles
• Ansible Extra Vars: Pass Variables from Command Line • Ansible set_fact Module: Set Variables Dynamically • Ansible default() Filter: Set Fallback Values • Ansible register: Save Task Output to Variables • Ansible Configuration: ansible.cfg Complete GuideSee also
• Ansible group_vars & host_vars: Organize Variables by Host and GroupCategory: database-automation