Ansible Variable Precedence
Ansible lets you define the same variable in more than twenty places. A variable named http_port can appear in a role default, an inventory file, a playbook, and a command line flag at the same moment. Ansible must pick one value, and a fixed ranking decides the winner. Imagine a school where the class rule says "no phones", the principal says "phones allowed at lunch", and a teacher grants a one-day exception. The most specific and most recent instruction wins. Variable precedence works the same way.
The Ranking in Plain Terms
Higher entries beat lower entries. The list below groups the full official order into the levels used in daily work.
- Command line values from
-e(extra vars) win over everything - Task level
vars,set_fact, andregisterresults - Block and play level
vars, plusvars_files - Host facts and variables in
host_vars - Variables in
group_vars(child groups beat parent groups) - Inventory file variables
- Role
varsfolder - Role
defaultsfolder, the lowest priority of all
Diagram: The Precedence Ladder
STRONGEST
┌─────────────────────────────┐
│ -e "http_port=9090" │ command line
├─────────────────────────────┤
│ set_fact / task vars │ task level
├─────────────────────────────┤
│ play vars / vars_files │ play level
├─────────────────────────────┤
│ host_vars/web1.yml │ one host
├─────────────────────────────┤
│ group_vars/webservers.yml │ one group
├─────────────────────────────┤
│ group_vars/all.yml │ every host
├─────────────────────────────┤
│ roles/web/defaults/main.yml │ role default
└─────────────────────────────┘
WEAKEST
Watch It Work
The same variable appears in three files.
# roles/web/defaults/main.yml
http_port: 80
# group_vars/webservers.yml
http_port: 8080
# host_vars/web1.yml
http_port: 8081
A playbook runs the debug module on every host in the webservers group.
- hosts: webservers
roles:
- web
tasks:
- ansible.builtin.debug:
msg: "Port is {{ http_port }}"
| Host | Winning Value | Reason |
|---|---|---|
| web1 | 8081 | host_vars beats group_vars |
| web2 | 8080 | group_vars beats role defaults |
Running ansible-playbook site.yml -e "http_port=9090" changes both hosts to 9090 because extra vars sit at the top.
Group Variable Merging
A host often belongs to several groups. Ansible merges group variables in this order: the all group, parent groups, and child groups last. A child group overrides its parent. Groups at the same level merge in alphabetical order, so the later name wins. Set ansible_group_priority inside a group to change that outcome.
Defaults Versus Vars in Roles
A role has two variable folders with different jobs.
- defaults/main.yml holds friendly starting values that users are expected to override
- vars/main.yml holds internal values that rarely change and resist override from inventory
Put every tunable setting in defaults. Reserve vars for constants such as package names that differ per operating system.
Debug a Surprising Value
When a variable shows an unexpected value, search every location where it might live. Use the debug module to print the current value, and run with -v for extra detail.
ansible-inventory --host web1
ansible-playbook site.yml -v
grep -r "http_port" .
The ansible-inventory --host command prints every inventory level variable for one host, which narrows the search quickly.
Habits That Prevent Confusion
- Define each variable in as few places as possible
- Prefix role variables with the role name, such as
web_http_port - Reserve
-efor one-off overrides and emergency fixes - Avoid
set_factfor values that belong in inventory
Try This
Create a role with http_port: 80 in defaults. Add the same variable to group_vars/all.yml with the value 81. Run a debug task and observe 81. Add the variable to a play vars block with the value 82 and rerun. Finish with -e http_port=83. Each step shows one rung of the ladder.
Summary
Ansible resolves variable conflicts through a fixed ranking. Command line extra vars win, and role defaults lose. Host variables beat group variables, and child groups beat parent groups. Keep definitions in few places, use clear prefixes, and rely on ansible-inventory --host to investigate unexpected values.
