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.

  1. Command line values from -e (extra vars) win over everything
  2. Task level vars, set_fact, and register results
  3. Block and play level vars, plus vars_files
  4. Host facts and variables in host_vars
  5. Variables in group_vars (child groups beat parent groups)
  6. Inventory file variables
  7. Role vars folder
  8. Role defaults folder, 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 }}"
HostWinning ValueReason
web18081host_vars beats group_vars
web28080group_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 -e for one-off overrides and emergency fixes
  • Avoid set_fact for 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.