Introduction
Ansible variables can live in many places: group_vars/, host_vars/, role defaults/, role vars/, playbook vars:, and more. Putting variables in the wrong place leads to precedence confusion and hard-to-debug overrides. This guide shows exactly where each type of variable belongs.
Quick Comparison
| Location | Scope | Precedence | Purpose |
|---|---|---|---|
Role defaults/ | Role | 2 (lowest) | Sensible defaults users should override |
group_vars/all | All hosts | 4 | Organization-wide defaults |
group_vars/<group> | Group | 10-12 | Environment/team-specific values |
host_vars/<host> | Single host | 13 | Host-specific overrides |
Play vars: | Play | 14 | Play-specific values |
Role vars/ | Role | 18 | Internal role constants (hard to override) |
set_fact | Host | 19 | Runtime-computed values |
Extra vars -e | Global | 22 (highest) | CLI overrides |
Directory Structure
inventory/
├── production/
│ ├── hosts.yml
│ ├── group_vars/
│ │ ├── all.yml # All production hosts
│ │ ├── all/
│ │ │ ├── vars.yml # Split into files for organization
│ │ │ └── vault.yml # Encrypted secrets
│ │ ├── webservers.yml # Web server group
│ │ ├── dbservers.yml # Database group
│ │ └── monitoring.yml # Monitoring group
│ └── host_vars/
│ ├── web1.example.com.yml # Host-specific
│ └── db1.example.com.yml
├── staging/
│ ├── hosts.yml
│ ├── group_vars/
│ │ └── all.yml
│ └── host_vars/
│
roles/
├── nginx/
│ ├── defaults/main.yml # User-overridable defaults
│ └── vars/main.yml # Internal constants
group_vars — Environment & Team Variables
# group_vars/all.yml — applies to every host
---
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
dns_servers:
- 8.8.8.8
- 8.8.4.4
timezone: UTC
admin_email: ops@example.com
# group_vars/webservers.yml — only web servers
---
http_port: 80
https_port: 443
app_port: 8080
max_connections: 1024
ssl_cert_path: /etc/ssl/certs/site.crt
# group_vars/dbservers.yml — only database servers
---
pg_version: 16
pg_port: 5432
pg_max_connections: 200
pg_data_dir: /var/lib/postgresql/16/main
backup_schedule: "0 2 * * *"
Splitting Large group_vars
# group_vars/all/vars.yml — non-secret variables
---
env: production
deploy_user: deploy
app_version: "2.5.0"
# group_vars/all/vault.yml — encrypted with ansible-vault
---
vault_db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
vault_api_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
host_vars — Host-Specific Overrides
# host_vars/web1.example.com.yml
---
# Override group default
max_connections: 2048 # This host handles more traffic
# Host-specific settings
ansible_host: 10.0.1.10
custom_vhosts:
- name: api.example.com
port: 8081
- name: admin.example.com
port: 8082
# host_vars/db1.example.com.yml
---
pg_max_connections: 500 # Primary DB needs more connections
pg_wal_level: replica
pg_replicas:
- db2.example.com
- db3.example.com
Role defaults — Overridable Defaults
# roles/nginx/defaults/main.yml
# LOWEST precedence — designed to be overridden
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: "10m"
nginx_gzip: true
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_log_format: combined
# Users override in group_vars or host_vars:
# group_vars/webservers.yml:
# nginx_client_max_body_size: "100m"
Role vars — Internal Constants
# roles/nginx/vars/main.yml
# HIGH precedence — not meant to be overridden
---
nginx_conf_dir: /etc/nginx
nginx_log_dir: /var/log/nginx
nginx_pid: /run/nginx.pid
nginx_user: www-data # Platform-specific
# roles/nginx/vars/Debian.yml — OS-specific constants
---
nginx_package: nginx-full
nginx_service: nginx
When to Use Each
Role defaults/ (precedence 2)
# ✅ Values users SHOULD customize per environment
nginx_worker_connections: 1024
app_log_level: info
backup_retention_days: 30
group_vars/ (precedence 10-12)
# ✅ Environment-specific: prod vs staging vs dev
env: production
deploy_branch: main
monitoring_enabled: true
# ✅ Group-specific: webservers vs dbservers
http_port: 80
pg_version: 16
host_vars/ (precedence 13)
# ✅ Truly host-specific: IPs, special config, overrides
ansible_host: 10.0.1.10
special_cron: "0 */4 * * *"
# ✅ One-off overrides for specific machines
max_connections: 4096 # This box has 128GB RAM
Role vars/ (precedence 18)
# ✅ Internal constants users shouldn't change
config_dir: /etc/myapp
required_packages:
- python3
- python3-pip
Common Mistakes
# ❌ Putting secrets in group_vars without vault
# group_vars/all.yml
db_password: "s3cret123" # Committed to git in plaintext!
# ✅ Use ansible-vault
# group_vars/all/vault.yml (encrypted)
vault_db_password: "s3cret123"
# group_vars/all/vars.yml (references vault)
db_password: "{{ vault_db_password }}"
# ❌ Using role vars/ for values users should customize
# roles/app/vars/main.yml
app_port: 8080 # Hard to override! (precedence 18)
# ✅ Use role defaults/ instead
# roles/app/defaults/main.yml
app_port: 8080 # Easy to override in group_vars (precedence 2)
# ❌ Duplicating the same variable in multiple places
# group_vars/all.yml: http_port: 80
# group_vars/webservers.yml: http_port: 80 # Redundant!
# host_vars/web1.yml: http_port: 80 # Even more redundant!
# ✅ Define once at the most general level, override only when different
Practical Example
# group_vars/all.yml
env: production
app_version: "2.5.0"
monitoring_enabled: true
# group_vars/webservers.yml
app_port: 8080
nginx_worker_connections: 2048
# host_vars/web1.example.com.yml
nginx_worker_connections: 4096 # High-traffic host
# roles/nginx/defaults/main.yml
nginx_worker_connections: 1024 # Sensible default
# Resolution for web1: 4096 (host_vars wins)
# Resolution for web2: 2048 (group_vars wins over role defaults)
# Resolution for db1: 1024 (role default, no override)
Related Articles
- Ansible Variables Guide
- Ansible set_fact vs vars vs extra vars
- Ansible Inventory Guide
- Ansible Roles Guide
Conclusion
Role defaults/ for values users should override (lowest precedence). group_vars/ for environment and team-specific settings. host_vars/ for individual host overrides. Role vars/ for internal constants that shouldn't change. The rule: define at the most general level, override at the most specific. Keep secrets in vault-encrypted files. Split large group_vars into separate files for readability.