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):

  1. Command-line values (-u, -b, etc. — not -e)
  2. Role defaults (roles/x/defaults/main.yml)
  3. Inventory file or script group vars
  4. Inventory group_vars/all
  5. Playbook group_vars/all
  6. Inventory group_vars/
  7. Playbook group_vars/
  8. Inventory file or script host vars
  9. Inventory host_vars/
  10. Playbook host_vars/
  11. Host facts / cached set_facts
  12. Play vars (vars: in a play)
  13. Play vars_prompt
  14. Play vars_files
  15. Role vars (roles/x/vars/main.yml)
  16. Block vars (inside block:)
  17. Task vars (on a specific task)
  18. include_vars
  19. set_facts / registered vars
  20. Role (and include_role) params
  21. include_params
  22. 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: 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/.

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: 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.

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

See also: Ansible default() Filter: Set Fallback Values for Undefined Variables

Best Practices

Use the Right Level for the Right Purpose

LocationPrecedenceUse For
roles/x/defaults/LowestDefaults users should override
group_vars/Low-mediumEnvironment-specific config
host_vars/MediumHost-specific overrides
Play vars:Medium-highPlay-specific settings
roles/x/vars/HighInternal role constants
set_factVery highRuntime-computed values
-e extra varsHighestEmergency 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

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.

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:

  1. Extra vars (-e) always win — use for CI/CD and emergency overrides
  2. Role defaults = meant to be overridden (lowest)
  3. Role vars = internal constants (high, hard to override)
  4. set_fact = runtime override (very high)
  5. group_vars/host_vars = environment/host config (medium)
  6. 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.

See also

Category: database-automation

Browse all Ansible tutorials · AnsiblePilot Home