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_varsset_facts/ registered vars- Role (and include_role) params
include_params- Extra vars (
-e/--extra-vars) — ALWAYS WINS
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: falseThis 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
- opensslroles/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: 9090set_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.ymlPlaybook-adjacent group_vars/ overrides inventory-adjacent group_vars/.
See also: Ansible vars_files: Load Variables from External YAML Files (Guide)
Practical Examples
Example 1: Role Default vs Group Var
# roles/webserver/defaults/main.yml
http_port: 80
# group_vars/production.yml
http_port: 8080Winner: 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: 9090Winner: 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_overrideWinner: 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: 9090Winner: 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=3000Winner: 3000 — extra vars (level 22) beats everything.
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 -vvvUse 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 dataSee also: Ansible default() Filter: Set Fallback Values for Undefined Variables
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 defaultsUse Prefixed Variable Names
# Bad: generic name, easy to collide
port: 8080
# Good: role-prefixed, unique
webserver_port: 8080
database_port: 5432Document 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: falseVariable 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 replacedTo 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.
See also: Ansible register: Save Task Output to Variables (Complete Guide)
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
-vvvto see what's happening
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 Guide
See also
Category: database-automation