CertGrid CertGrid
Configuration·Ansible

Ansible Configuration File Precedence

Ansible reads its configuration from the first of four locations it finds, and gets this wrong for you silently if you assume the wrong one. Rather than describe the order, this writes conflicting values into three of them and reads back which one took effect - because `ansible-config dump` prints the source of every setting in brackets next to its value.

Control Node and Managed Nodes Guide 4 of 45 Beginner

Written against the versions above. The search order is `ANSIBLE_CONFIG` (environment), then `./ansible.cfg` in the current directory, then `~/.ansible.cfg`, then `/etc/ansible/ansible.cfg`. **The first one found wins completely** - settings are not merged across files. Individual `ANSIBLE_*` environment variables beat all of them.

Configuration is a control-node concern. Nothing in this guide touches a managed node.
Server NameIP AddressOSRolesCPURAMHDD
ANS-CTL01192.168.0.36Ubuntu 26.04 LTSAnsible Control Node2 Core3 GB50 GB

Before you start

  1. Which file is in use

    Two commands answer it. The version banner:

      config file = /home/sysadmin/ansible.cfg

    and the dump of everything you have changed from the defaults:

    ansible-config dump --only-changed

    --only-changed is the useful one. A full dump is hundreds of settings; this shows just the handful you are responsible for, which is usually the whole explanation of why your Ansible behaves differently from someone else's.

    bash Example session
    ansible --version | head -3ansible [core 2.20.1]  config file = /home/sysadmin/ansible.cfg  configured module search path = ['/home/sysadmin/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']cat ~/ansible.cfg[defaults]inventory = ./inventory.inihost_key_checking = Falseremote_user = sysadminstdout_callback = yamlansible-config dump --only-changedCONFIG_FILE() = /home/sysadmin/ansible.cfgDEFAULT_HOST_LIST(/home/sysadmin/ansible.cfg) = ['/home/sysadmin/inventory.ini']DEFAULT_REMOTE_USER(/home/sysadmin/ansible.cfg) = sysadminDEFAULT_STDOUT_CALLBACK(/home/sysadmin/ansible.cfg) = yamlHOST_KEY_CHECKING(/home/sysadmin/ansible.cfg) = False GALAXY_SERVERS:

    Expected resultThe config path, its contents, and the settings it changed.

    Success conditionYou can answer 'which config is this using' immediately.

  2. The four places it looks

    ls: cannot access '/etc/ansible/ansible.cfg': No such file or directory
    ls: cannot access '/home/sysadmin/.ansible.cfg': No such file or directory

    Neither of the lower-priority locations exists here, which is why the project file is being used. On many systems /etc/ansible/ansible.cfg does exist from the package, and is the reason a fresh checkout behaves differently on someone else's machine.

    ansible-config init --disabled prints a fully commented template of every available setting - a good way to discover options, and far too long to keep as your actual config.

    bash Example session
    ansible-config init --disabled 2>/dev/null | head -5[defaults]# (boolean) By default, Ansible will issue a warning when received from a task action (module or action plugin).# These warnings can be silenced by adjusting this setting to False.;action_warnings=Truels -l /etc/ansible/ansible.cfg 2>&1ls: cannot access '/etc/ansible/ansible.cfg': No such file or directory[exit 2]ls -l ~/.ansible.cfg 2>&1ls: cannot access '/home/sysadmin/.ansible.cfg': No such file or directory[exit 2]

    Expected resultA commented template, and two locations that are empty here.

    Success conditionYou know all four candidates, not just the one you use.

  3. Prove the order

    Two files, two different values for the same setting - forks = 11 in ~/.ansible.cfg and forks = 22 in ./ansible.cfg:

    DEFAULT_FORKS(/home/sysadmin/ansible.cfg) = 22

    The value comes with its source in brackets. No guessing: the project file won, and ~/.ansible.cfg was not consulted at all.

    That parenthetical is the single most useful thing about ansible-config dump, and it settles arguments. When a setting is not doing what you expect, dump it and read where it came from.

    bash Example session
    printf '[defaults]\nforks = 11\n' > ~/.ansible.cfg && echo "wrote ~/.ansible.cfg with forks=11"wrote ~/.ansible.cfg with forks=11printf '[defaults]\ninventory = ./inventory.ini\nhost_key_checking = False\nremote_user = sysadmin\nstdout_callback = yaml\nforks = 22\n' > ~/ansible.cfg && echo "wrote ./ansible.cfg with forks=22"wrote ./ansible.cfg with forks=22ansible-config dump | grep -E "^DEFAULT_FORKS"DEFAULT_FORKS(/home/sysadmin/ansible.cfg) = 22ansible --version | grep "config file"  config file = /home/sysadmin/ansible.cfg

    Expected resultDEFAULT_FORKS(/home/sysadmin/ansible.cfg) = 22.

    Success conditionYou can prove which file supplied a setting.

  4. The environment beats them all

    DEFAULT_FORKS(/tmp/other.cfg) = 33
      config file = /tmp/other.cfg

    ANSIBLE_CONFIG replaces the whole search - the project file is ignored entirely. This is how CI pipelines pin a known configuration, and how you test one without touching your own.

    And a single setting can be overridden on its own:

    DEFAULT_FORKS(env: ANSIBLE_FORKS) = 44

    env: ANSIBLE_FORKS - the source again. Nearly every setting has an ANSIBLE_* variable, which beats every file. The full order, highest first:

    1. ANSIBLE_ environment variable
    2. ANSIBLE_CONFIG pointing at a file
    3. ./ansible.cfg
    4. ~/.ansible.cfg
    5. /etc/ansible/ansible.cfg

    And the trap: files do not merge. If ./ansible.cfg sets one thing, nothing is inherited from the others - a project config with only inventory in it silently discards every setting you had in ~/.ansible.cfg.

    bash Example session
    printf '[defaults]\ninventory = /home/sysadmin/inventory.ini\nforks = 33\n' > /tmp/other.cfg && ANSIBLE_CONFIG=/tmp/other.cfg ansible-config dump | grep -E "^DEFAULT_FORKS"DEFAULT_FORKS(/tmp/other.cfg) = 33ANSIBLE_CONFIG=/tmp/other.cfg ansible --version | grep "config file"  config file = /tmp/other.cfgANSIBLE_FORKS=44 ansible-config dump | grep -E "^DEFAULT_FORKS"DEFAULT_FORKS(env: ANSIBLE_FORKS) = 44

    Expected resultThe source shown as a different file, then as an environment variable.

    Success conditionYou can override configuration for one run without editing anything.

  5. The settings worth knowing

    inventory = ./inventory.ini
    host_key_checking = False
    remote_user = sysadmin
    stdout_callback = yaml

    Four lines that make a lab usable:

    • inventory - stop typing -i on every command
    • host_key_checking - off here for convenience; leave it on anywhere real, because it is what stops you executing against an impostor
    • remote_user - who to log in as
    • stdout_callback = yaml - readable multi-line output instead of dense JSON, and worth setting on the first day

    Two more you will want soon: forks (how many hosts at once, default 5) and pipelining (fewer SSH round trips per task).

    The files written during this guide are removed and the original restored, which the final --only-changed confirms.

    bash Example session
    ansible-config list 2>/dev/null | grep -cE "^[A-Z_]+:"214ansible-config dump | grep -E "^(DEFAULT_HOST_LIST|HOST_KEY_CHECKING|DEFAULT_REMOTE_USER|DEFAULT_STDOUT_CALLBACK|DEFAULT_FORKS|DEFAULT_TIMEOUT) " [exit 1]rm -f ~/.ansible.cfg /tmp/other.cfgprintf '[defaults]\ninventory = ./inventory.ini\nhost_key_checking = False\nremote_user = sysadmin\nstdout_callback = yaml\n' > ~/ansible.cfg && ansible-config dump --only-changedCONFIG_FILE() = /home/sysadmin/ansible.cfgDEFAULT_HOST_LIST(/home/sysadmin/ansible.cfg) = ['/home/sysadmin/inventory.ini']DEFAULT_REMOTE_USER(/home/sysadmin/ansible.cfg) = sysadminDEFAULT_STDOUT_CALLBACK(/home/sysadmin/ansible.cfg) = yamlHOST_KEY_CHECKING(/home/sysadmin/ansible.cfg) = False GALAXY_SERVERS:

    Expected resultA count of available settings, the ones in force, and a clean restore.

    Success conditionYou have a config you can explain line by line.

Troubleshooting

Official sources