Introduction

ansible.cfg controls Ansible's behavior — inventory location, SSH settings, privilege escalation, performance tuning, and more. Place it in your project directory for per-project configuration.

Configuration Precedence

Ansible checks these locations (first match wins):

  1. ANSIBLE_CONFIG environment variable
  2. ./ansible.cfg (current directory)
  3. ~/.ansible.cfg (home directory)
  4. /etc/ansible/ansible.cfg (global)

Best practice: Put ansible.cfg in your project root.

Essential Configuration

# ansible.cfg
[defaults]
# Inventory file
inventory = inventory/hosts.yml

# Disable host key checking (for lab/dev)
host_key_checking = False

# SSH user
remote_user = deploy

# Roles path
roles_path = roles:~/.ansible/roles

# Collections path
collections_path = collections

# Retry files (disable)
retry_files_enabled = False

# Output formatting
stdout_callback = yaml
# or: json, debug, dense, minimal

# Fact caching
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 86400

# Display skipped hosts
display_skipped_hosts = False

# Vault password file
vault_password_file = .vault-pass

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
# SSH pipelining (major performance boost)
pipelining = True

# SSH arguments
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s

# SCP vs SFTP
transfer_method = smart

# Connection timeout
timeout = 30

Performance Tuning

[defaults]
# Parallel execution (default: 5)
forks = 20

# Smart fact gathering (cache facts)
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 3600

# Callback plugins for speed
stdout_callback = yaml
callback_whitelist = timer, profile_tasks

# Disable cowsay
nocows = True

[ssh_connection]
# SSH pipelining (reduces SSH operations)
pipelining = True

# Persistent connections
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s

# Max connections
control_path_dir = /tmp/.ansible-cp

Impact of Settings

SettingImpact
forks = 20Run on 20 hosts simultaneously (vs 5 default)
pipelining = True~2x speed (fewer SSH round-trips)
gathering = smartOnly gather facts once (cached)
ControlPersist=600sKeep SSH connections open 10 min

Security Settings

[defaults]
# Don't log sensitive data
no_log = False
display_args_to_stdout = False

# Vault
vault_password_file = .vault-pass

# Host key checking (enable in production!)
host_key_checking = True

[privilege_escalation]
become = False           # Don't default to root
become_ask_pass = True   # Ask for sudo password

Inventory Configuration

[defaults]
inventory = inventory/

[inventory]
# Enable inventory plugins
enable_plugins = host_list, yaml, ini, auto, aws_ec2, azure_rm

# Cache dynamic inventory
cache = True
cache_plugin = jsonfile
cache_connection = /tmp/ansible-inventory-cache
cache_timeout = 3600

Logging

[defaults]
# Log all output
log_path = /var/log/ansible.log

# Task profiling
callback_whitelist = timer, profile_tasks, profile_roles

# Verbose by default
# verbosity = 1

Per-Environment Config

project/
├── ansible.cfg              # Default config
├── ansible-dev.cfg          # Dev overrides
├── inventory/
│   ├── production/
│   └── staging/
└── playbooks/
# Use specific config
ANSIBLE_CONFIG=ansible-dev.cfg ansible-playbook site.yml

Common Patterns

Development Config

[defaults]
inventory = inventory/dev.yml
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
forks = 10

[privilege_escalation]
become = True
become_ask_pass = False

Production Config

[defaults]
inventory = inventory/production/
host_key_checking = True
retry_files_enabled = True
log_path = /var/log/ansible/production.log
vault_password_file = .vault-pass
forks = 30
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
callback_whitelist = timer, profile_tasks

[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False

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

CI/CD Config

[defaults]
inventory = inventory/
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
forks = 50
gathering = smart

[ssh_connection]
pipelining = True
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no

Environment Variables

Override any setting with ANSIBLE_ prefix:

export ANSIBLE_FORKS=50
export ANSIBLE_HOST_KEY_CHECKING=False
export ANSIBLE_STDOUT_CALLBACK=yaml
export ANSIBLE_VAULT_PASSWORD_FILE=.vault-pass
export ANSIBLE_CONFIG=/path/to/ansible.cfg

View Current Config

# Show all config
ansible-config dump

# Show only changed values
ansible-config dump --only-changed

# Show config file location
ansible-config view

# List all settings
ansible-config list

Troubleshooting

Config Not Being Read

# Check which config is active
ansible-config dump --only-changed | head -5
# First line shows config file path

# Check precedence
ansible --version
# Shows "config file = /path/to/ansible.cfg"

Pipelining Fails

Pipelining requires requiretty to be disabled in sudoers:

# Fix via playbook
- name: Disable requiretty
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: "^Defaults.*requiretty"
    line: "Defaults !requiretty"
    validate: "visudo -cf %s"

Conclusion

ansible.cfg is the control center for Ansible behavior. Put it in your project root. The three biggest performance wins: forks = 20+, pipelining = True, and gathering = smart. Use stdout_callback = yaml for readable output. Enable host_key_checking in production. Keep per-project configs for different environments. Use ansible-config dump --only-changed to verify your settings are applied.