AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

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 LineAnsible set_fact Module: Set Variables DynamicallyAnsible default() Filter: Set Fallback ValuesAnsible register: Save Task Output to VariablesAnsible Configuration: ansible.cfg Complete Guide

See also

Ansible group_vars & host_vars: Organize Variables by Host and Group

Category: database-automation

Browse all Ansible tutorials · AnsiblePilot Home