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.cfg- the settings file controlling Ansible's own behaviour.- Search order - the fixed sequence of locations Ansible checks. The FIRST file found wins entirely.
- *
ANSIBLE_environment variables** - override individual settings, and outrank the config file. ansible-config dump- prints every effective setting and where it came from.
Ansible looks for its configuration in a strict order and stops at the first hit:
ANSIBLE_CONFIGenvironment variableansible.cfgin the current working directory~/.ansible.cfg/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, pickansible-facts-performance, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.