Where on disk does Ansible look for `group_vars/` and `host_vars/`, and which file wins when a host belongs to several groups?
answer
- directories, not a config setting
- two roots are searched, not one
- one of them beats the other
- filename must equal the group or host name
- closer to the host wins
basics
~20 sAnsible loads group_vars/ and host_vars/ from two places: the directory holding the inventory and the directory holding the playbook, with the playbook-adjacent copy winning. Across groups, a child group's file beats its parent's, and host_vars beat every group file.
solid answer
~40 sThey are directories, not settings. Ansible looks for `group_vars/` and `host_vars/` next to the inventory file or inventory directory, and also next to the playbook; if both exist, the playbook-adjacent one wins. Inside them, `group_vars/webservers.yml` applies to that group, `group_vars/all.yml` to every host, and `host_vars/web01.yml` to the single host whose *inventory name* is `web01` — the alias, not `ansible_host`. Each can also be a directory of files loaded in alphabetical order, which is how teams split large variable sets. When a host is in several groups, more specific wins: `all` first, then parent groups, then child groups, then host_vars. Same-depth groups are resolved alphabetically unless you set `ansible_group_priority`. And the merge is a replace, not a deep merge — a dict in a child group replaces the parent's dict wholesale.
code
bash · 8 linesinventories/prod/hosts.yml
inventories/prod/group_vars/all.yml
inventories/prod/group_vars/webservers.yml
inventories/prod/host_vars/web01.yml
site.yml
ansible-playbook -i inventories/prod site.yml
ansible-inventory -i inventories/prod --list --yamlgo deeper
Know that group_vars/<group>.yml and host_vars/<host>.yml set variables for a group or a single host, and that group_vars/all.yml applies everywhere.
Explain that Ansible searches beside both the inventory and the playbook, that the playbook-adjacent copy wins, and that a child group's value overrides its parent's.
Show you debug this by dumping resolved variables with ansible-inventory --list --yaml rather than sprinkling debug tasks, and that you keep var directories inside each environment's inventory directory.
Own the layout convention for the estate: where variable files live, whether duplication across environments is preferred to sharing, and how same-depth group collisions are prevented rather than discovered.
## They are directories, and location matters `group_vars` and `host_vars` are not configuration keys — they are directory names Ansible searches for automatically. It searches two roots: 1. the directory containing the inventory you passed to `-i` (if `-i` is a directory, that directory itself), 2. the directory containing the playbook you are running. Both are loaded if both exist, and the playbook-adjacent files take precedence over the inventory-adjacent ones. This single fact explains most "my variable is being ignored" reports: someone put `group_vars/prod.yml` at the repository root while the inventory lives in `inventories/prod/hosts.yml`, so the file the *inventory* would have loaded was never there, or a stale playbook-level copy is quietly overriding it. A typical layout that keeps the two straight: ``` inventories/ prod/ hosts.yml group_vars/ all.yml webservers.yml host_vars/ web01.yml staging/ hosts.yml group_vars/ all.yml site.yml ``` Here `-i inventories/prod` finds the prod variables and nothing else. Switching to `-i inventories/staging` swaps the entire variable set with it, which is the point. ## File naming rules The file base name must equal the group or host name exactly: `group_vars/webservers.yml` for the group `webservers`, `host_vars/web01.yml` for the host whose inventory name is `web01`. Extensions `.yml`, `.yaml`, `.json` or none are all accepted. A typo produces no error at all — the file is simply never loaded, which is the second most common failure after wrong directory. Two names are worth calling out. `group_vars/all.yml` applies to every host, because every host is in the implicit `all` group. And `host_vars` keys off the inventory *name*: for the entry `web01 ansible_host=10.0.1.11`, the file is `host_vars/web01.yml`, never `host_vars/10.0.1.11.yml`. Either name can also be a directory: ``` group_vars/ webservers/ 00-network.yml 10-tuning.yml ``` All files inside are loaded in alphabetical order, later files overriding earlier ones for the same key. Teams use this to keep large groups readable, and to isolate an encrypted file from plain ones. ## Resolution when a host is in several groups Within these inventory variable files the rule is *more specific wins*: - `group_vars/all` is applied first, - then parent groups, then their children — a child group's value overrides its parent's, - then `host_vars`, which beat every group file. When two groups sit at the same depth and neither is an ancestor of the other, Ansible applies them in alphabetical order by group name, so the alphabetically last one wins. That is deliberate but rarely what a reader expects, so Ansible offers `ansible_group_priority` — set it in a group's vars (default `1`, higher wins) to break same-depth ties explicitly instead of relying on the group's name. ## Replace, not deep merge By default a value defined in a more specific place replaces the less specific one outright, including dictionaries. If `group_vars/all.yml` defines `app: {port: 8080, workers: 4}` and `group_vars/webservers.yml` defines `app: {port: 9090}`, hosts in `webservers` see only `port: 9090` — `workers` is gone. There is a global hash-behaviour setting that switches this to merging, but flipping it changes the semantics of every variable in the project and is widely discouraged; the maintainable fix is to flatten the keys (`app_port`, `app_workers`) or merge explicitly where you need it. ## Practical checks `ansible-inventory -i inventories/prod --list --yaml` dumps the fully resolved variables per host, which settles any argument about which file won. `ansible-inventory --graph --vars` shows the same information laid out over the group tree. Reaching for either takes seconds and beats adding debug tasks to a playbook.
- A host is listed as `web01 ansible_host=10.0.1.11`. What must the host_vars file be called?`host_vars/web01.yml`. The lookup uses the inventory name — the label in the inventory — not the address in `ansible_host`. Naming the file after the IP means it is never loaded, and Ansible reports no error because a missing host_vars file is a normal condition.
- Two groups at the same level both set `log_level` and neither is a parent of the other. Which value applies?Ansible applies same-depth groups in alphabetical order, so the alphabetically last group name wins — which is an accident of naming rather than intent. Set `ansible_group_priority` in one group's vars (default 1, higher wins) to make the tie-break explicit and reviewable.
- Why is defining the same dictionary in a parent and a child group risky?Because the default behaviour is replace, not deep merge: the child's dict wholly replaces the parent's, silently dropping keys the parent defined. Flatten the keys or merge deliberately rather than switching the global hash-behaviour setting, which changes semantics for every variable in the project.
saying these in an interview costs you the question
- Thinking group_vars is only ever a repo-root directory
- Naming host_vars files after ansible_host instead of the inventory name
- Expecting dictionaries from parent and child groups to deep-merge
- Believing a parent group overrides its child
- Assuming a misspelled group_vars filename raises an error