Introduction

The ansible.builtin.fetch module copies files from remote hosts to the Ansible control node — the reverse of the copy module. It's essential for collecting logs, configuration files, certificates, database dumps, and any file you need to retrieve from managed hosts.

Module Reference

Full name: ansible.builtin.fetch Collection: ansible.builtin

Parameters

ParameterTypeRequiredDefaultDescription
srcstringYes—Remote file path to fetch (must be a file, not directory)
destpathYes—Local directory to save fetched files
flatboolNofalseSave directly to dest without hostname subdirectory
fail_on_missingboolNotrueFail if remote file doesn't exist
validate_checksumboolNotrueVerify file integrity after transfer

Default Directory Structure

Without flat: true, fetch creates a directory structure:

dest/
  hostname1/
    var/log/messages
  hostname2/
    var/log/messages

This prevents filename collisions when fetching the same file from multiple hosts.

Basic Playbook

---
- name: Fetch log files
  hosts: all
  become: true
  vars:
    log_file: "/var/log/messages"
    dump_dir: "logs"
  tasks:
    - name: Fetch system log
      ansible.builtin.fetch:
        src: "{{ log_file }}"
        dest: "{{ dump_dir }}"

Result:

logs/
  server1/var/log/messages
  server2/var/log/messages

Using flat Mode

When fetching a unique file (or from a single host), use flat: true to save directly:

- name: Fetch config file (flat)
  ansible.builtin.fetch:
    src: /etc/nginx/nginx.conf
    dest: ./backups/nginx-{{ inventory_hostname }}.conf
    flat: true

Result: ./backups/nginx-web01.conf

Important: With flat: true and multiple hosts, you must include {{ inventory_hostname }} in dest to avoid overwriting files.

Practical Use Cases

Collect Logs for Analysis

- name: Collect logs from all web servers
  hosts: web_servers
  become: true
  tasks:
    - name: Fetch access logs
      ansible.builtin.fetch:
        src: /var/log/nginx/access.log
        dest: "./collected-logs/"

    - name: Fetch error logs
      ansible.builtin.fetch:
        src: /var/log/nginx/error.log
        dest: "./collected-logs/"

Backup Configuration Before Changes

- name: Backup and modify config
  hosts: db_servers
  become: true
  tasks:
    - name: Backup current PostgreSQL config
      ansible.builtin.fetch:
        src: /etc/postgresql/16/main/postgresql.conf
        dest: "./backups/{{ ansible_date_time.date }}/"

    - name: Apply new configuration
      ansible.builtin.template:
        src: postgresql.conf.j2
        dest: /etc/postgresql/16/main/postgresql.conf
      notify: Restart PostgreSQL

Retrieve SSL Certificates

- name: Collect SSL certificates for audit
  ansible.builtin.fetch:
    src: /etc/ssl/certs/server.crt
    dest: "./certs/{{ inventory_hostname }}.crt"
    flat: true

Fetch Database Dump

- name: Create and fetch database backup
  hosts: db_primary
  become: true
  become_user: postgres
  tasks:
    - name: Create database dump
      ansible.builtin.command: >
        pg_dump -Fc mydb -f /tmp/mydb_backup.dump
      changed_when: true

    - name: Fetch backup to control node
      ansible.builtin.fetch:
        src: /tmp/mydb_backup.dump
        dest: "./db-backups/mydb-{{ ansible_date_time.iso8601_basic_short }}.dump"
        flat: true

    - name: Clean up remote dump
      ansible.builtin.file:
        path: /tmp/mydb_backup.dump
        state: absent

Fetch Multiple Files with a Loop

- name: Fetch multiple config files
  ansible.builtin.fetch:
    src: "{{ item }}"
    dest: "./configs/"
  loop:
    - /etc/hosts
    - /etc/resolv.conf
    - /etc/hostname
    - /etc/sysctl.conf

Conditional Fetch

- name: Check if crash dump exists
  ansible.builtin.stat:
    path: /var/crash/vmcore
  register: crash_dump

- name: Fetch crash dump if present
  ansible.builtin.fetch:
    src: /var/crash/vmcore
    dest: "./crash-dumps/"
  when: crash_dump.stat.exists

Handling Large Files

For very large files, consider compressing first:

- name: Compress log before fetching
  ansible.builtin.archive:
    path: /var/log/myapp/
    dest: /tmp/logs-{{ inventory_hostname }}.tar.gz
  register: archive_result

- name: Fetch compressed logs
  ansible.builtin.fetch:
    src: /tmp/logs-{{ inventory_hostname }}.tar.gz
    dest: "./logs/"
    flat: true

- name: Clean up compressed file
  ansible.builtin.file:
    path: /tmp/logs-{{ inventory_hostname }}.tar.gz
    state: absent

Troubleshooting

"Checksum mismatch"

fatal: [host]: FAILED! => {"msg": "checksum mismatch", ...}

The file changed during transfer. Options:

  • Retry the task
  • Disable checksum: validate_checksum: false (not recommended for critical files)
  • Stop the service writing to the file first

"File not found"

fatal: [host]: FAILED! => {"msg": "the remote file does not exist"}

The source file doesn't exist. Use fail_on_missing: false to skip gracefully:

- name: Fetch optional log
  ansible.builtin.fetch:
    src: /var/log/optional.log
    dest: "./logs/"
    fail_on_missing: false

Cannot Fetch Directories

The fetch module only handles single files. For directories, use synchronize:

- name: Fetch entire directory
  ansible.posix.synchronize:
    src: /var/log/myapp/
    dest: ./logs/{{ inventory_hostname }}/
    mode: pull

fetch vs Other File Transfer Modules

TaskModule
Remote → Local (single file)fetch
Local → Remotecopy
Local template → Remotetemplate
Bidirectional syncsynchronize (rsync)
Download URL to remoteget_url
Download URL to remoteuri (with dest)

Conclusion

The ansible.builtin.fetch module is the standard tool for pulling files from remote hosts to your control node. Use the default directory structure (with hostname paths) when fetching from multiple hosts, and flat: true when you need direct file paths. Common patterns include log collection, pre-change configuration backups, database dump retrieval, and certificate auditing. For directories, use synchronize instead.