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

IssueSolution
Defaults not appliedCheck FQCN matches exactly (ansible.builtin.uri not uri)
Override not workingTask params always win over defaults
Group defaults not foundUse group/collection.name format
Defaults leak between blocksBlock-level defaults scope to that block only
Wrong module gets defaultsEach module key is independent; check module name spelling

Best Practices

  1. Use FQCNs — ansible.builtin.uri not uri
  2. API credentials at play level — avoid repeating auth headers
  3. Cloud region/profile at play level — consistent across all resources
  4. Block-level for different APIs — separate auth for internal vs external
  5. Don't over-default — only default values used by 3+ tasks
  6. 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.