ansible.cfg and Environment Precedence

Scenario: A playbook behaves differently for one engineer. Everyone else's runs use the project inventory; theirs silently uses a path from a ~/.ansible.cfg written eighteen months ago.

New words, in plain English

Ansible looks for its configuration in a strict order and stops at the first hit:

  1. ANSIBLE_CONFIG environment variable
  2. ansible.cfg in the current working directory
  3. ~/.ansible.cfg
  4. /etc/ansible/ansible.cfg

The critical detail is that these are not merged. The first file found supplies every setting; the others are ignored entirely. That is the scenario: a project ansible.cfg is only used if you run from the project directory, and a stale ~/.ansible.cfg takes over when you do not.

Individual ANSIBLE_ environment variables outrank the config file - ANSIBLE_FORKS=50, ANSIBLE_HOST_KEY_CHECKING=False. These are per-setting, so they do* layer on top rather than replacing everything.

One security note: Ansible deliberately ignores an ansible.cfg in a world-writable directory, because reading configuration from a directory anyone can write to is a code-execution risk. If your project config seems inexplicably ignored, check the directory permissions.

The settings worth having in a project config: inventory, roles_path, collections_path, forks, pipelining, host_key_checking, vault_password_file, fact caching, and a callback such as profile_tasks.

Analogy: Not a stack of memos where the newest amends the oldest - a single memo, and whichever one you find first is the only one you read. A forgotten memo in your desk drawer beats the one pinned to the office wall.

A worked example

# The search order - FIRST match wins entirely, no merging
#   1. $ANSIBLE_CONFIG
#   2. ./ansible.cfg          <- project config, only if run from here
#   3. ~/.ansible.cfg         <- the usual culprit
#   4. /etc/ansible/ansible.cfg

# A sensible project ansible.cfg
[defaults]
inventory            = ./inventory
roles_path           = ./roles
collections_path     = ./collections
forks                = 30
host_key_checking    = True        # keep True in production
vault_password_file  = ~/.vault_pass
gathering            = smart
fact_caching         = jsonfile
fact_caching_connection = /var/tmp/ansible_facts
callbacks_enabled    = ansible.posix.profile_tasks

[ssh_connection]
pipelining = True
ssh_args   = -o ControlMaster=auto -o ControlPersist=120s

# Settle any argument about effective settings:
ansible-config dump --only-changed
ansible-config dump | grep -i forks

ansible-config dump --only-changed is the diagnostic. It prints every non-default setting and the source that supplied it, which answers "why is it behaving like that on your machine?" in one command.

A note on host_key_checking. Setting it to False is common in lab and CI environments and is genuinely convenient. In production it removes protection against a man-in-the-middle on your management path, which is the one connection you most want to be certain about. Manage known_hosts properly rather than disabling the check - and if you must disable it somewhere, make sure it is scoped to that environment rather than sitting in a config everyone inherits.

Warning: Config files are never merged. A stale ~/.ansible.cfg silently replaces your entire project configuration whenever someone runs from outside the project directory.
Goal: Put this to work in the ansible-facts-performance lab. Open /labs/ansible, pick ansible-facts-performance, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.