Ansible module_defaults — Set Default Parameters for Modules
Introduction
When multiple tasks use the same module with identical parameters — API headers, connection strings, become settings — module_defaults sets those values once at the play or block level. Tasks inherit the defaults automatically, reducing repetition and making playbooks easier to maintain.
Basic Usage
---
- name: API management
hosts: localhost
connection: local
module_defaults:
ansible.builtin.uri:
headers:
Authorization: "Bearer {{ api_token }}"
Content-Type: "application/json"
validate_certs: true
timeout: 30
tasks:
# All uri tasks inherit headers, validate_certs, timeout
- name: Get users
ansible.builtin.uri:
url: "https://api.example.com/users"
register: users
- name: Get settings
ansible.builtin.uri:
url: "https://api.example.com/settings"
register: settings
- name: Update config
ansible.builtin.uri:
url: "https://api.example.com/config"
method: POST
body_format: json
body:
key: value
Without module_defaults, you'd repeat the headers block in every task.
Block-Level Defaults
tasks:
- name: Internal API calls
module_defaults:
ansible.builtin.uri:
headers:
Authorization: "Bearer {{ internal_token }}"
block:
- name: Call internal service A
ansible.builtin.uri:
url: "https://internal.example.com/service-a"
- name: Call internal service B
ansible.builtin.uri:
url: "https://internal.example.com/service-b"
- name: External API calls
module_defaults:
ansible.builtin.uri:
headers:
X-API-Key: "{{ external_key }}"
block:
- name: Call external service
ansible.builtin.uri:
url: "https://external.example.com/data"
Multiple Modules
- name: Cloud provisioning
hosts: localhost
connection: local
module_defaults:
amazon.aws.ec2_instance:
region: us-east-1
key_name: deploy-key
vpc_subnet_id: subnet-abc123
amazon.aws.ec2_security_group:
region: us-east-1
vpc_id: vpc-xyz789
ansible.builtin.uri:
headers:
Authorization: "Bearer {{ monitoring_token }}"
tasks:
- name: Create security group
amazon.aws.ec2_security_group:
name: web-sg
description: Web servers
rules:
- proto: tcp
ports: [80, 443]
cidr_ip: 0.0.0.0/0
- name: Launch instance
amazon.aws.ec2_instance:
name: web-01
instance_type: t3.micro
image_id: ami-12345
security_group: web-sg
Group Defaults (Action Groups)
# Set defaults for ALL AWS modules at once
- name: AWS infrastructure
hosts: localhost
module_defaults:
group/amazon.aws.aws:
region: us-east-1
profile: production
tasks:
- name: Create VPC
amazon.aws.ec2_vpc_net:
name: production
cidr_block: 10.0.0.0/16
# Inherits region and profile
- name: Create subnet
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc.vpc.id }}"
cidr: 10.0.1.0/24
# Inherits region and profile
- name: Launch instance
amazon.aws.ec2_instance:
name: web-01
instance_type: t3.micro
# Inherits region and profile
Override Defaults Per Task
module_defaults:
ansible.builtin.uri:
timeout: 30
validate_certs: true
tasks:
- name: Normal API call (uses defaults)
ansible.builtin.uri:
url: "https://api.example.com/data"
- name: Internal call (override validate_certs)
ansible.builtin.uri:
url: "https://internal.local/health"
validate_certs: false # Overrides the default
timeout: 5 # Overrides the default
Package Management
- name: Server setup
hosts: all
module_defaults:
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
ansible.builtin.systemd:
enabled: true
state: started
tasks:
- name: Install Nginx
ansible.builtin.apt:
name: nginx
- name: Install PostgreSQL
ansible.builtin.apt:
name: postgresql
- name: Enable Nginx
ansible.builtin.systemd:
name: nginx
- name: Enable PostgreSQL
ansible.builtin.systemd:
name: postgresql
File Operations
module_defaults:
ansible.builtin.copy:
owner: www-data
group: www-data
mode: '0644'
ansible.builtin.template:
owner: www-data
group: www-data
mode: '0644'
tasks:
- name: Deploy index.html
ansible.builtin.copy:
src: index.html
dest: /var/www/html/index.html
- name: Deploy config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
mode: '0640' # Override for sensitive config
Troubleshooting
| Issue | Solution |
|---|---|
| Defaults not applied | Check FQCN matches exactly (ansible.builtin.uri not uri) |
| Override not working | Task params always win over defaults |
| Group defaults not found | Use group/collection.name format |
| Defaults leak between blocks | Block-level defaults scope to that block only |
| Wrong module gets defaults | Each module key is independent; check module name spelling |
Best Practices
- Use FQCNs —
ansible.builtin.urinoturi - API credentials at play level — avoid repeating auth headers
- Cloud region/profile at play level — consistent across all resources
- Block-level for different APIs — separate auth for internal vs external
- Don't over-default — only default values used by 3+ tasks
- Document overrides — comment when a task intentionally overrides defaults
Conclusion
module_defaults eliminates parameter repetition — set API headers, cloud regions, file permissions, or connection settings once, and every matching module task inherits them automatically. Use play-level defaults for global settings, block-level for scoped overrides, and task-level parameters when you need exceptions. It's the DRY principle applied to Ansible module parameters.