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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
src | string | Yes | — | Remote file path to fetch (must be a file, not directory) |
dest | path | Yes | — | Local directory to save fetched files |
flat | bool | No | false | Save directly to dest without hostname subdirectory |
fail_on_missing | bool | No | true | Fail if remote file doesn't exist |
validate_checksum | bool | No | true | Verify 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
| Task | Module |
|---|---|
| Remote → Local (single file) | fetch |
| Local → Remote | copy |
| Local template → Remote | template |
| Bidirectional sync | synchronize (rsync) |
| Download URL to remote | get_url |
| Download URL to remote | uri (with dest) |
Related Articles
- Copy Files to Remote Hosts: Ansible copy Module
- Create an Empty File: Ansible file Module
- Ansible template Module Guide
- How to Check If a Directory Exists
- Ansible Backup Windows Guide
- Remove a File: Ansible file Module
- Ansible Best Practices Guide
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.