Transferring Files Securely: Using the Ansible Copy and Fetch Modules
Use Ansible copy and fetch ad-hoc commands to push files to managed nodes and retrieve logs or configs back to your control node.
Transferring Files Securely: Using the Ansible Copy and Fetch Modules
Moving files is one of the first practical Ansible tasks you need: push a config to your servers, collect a log file, or back up a remote file before a change. The copy and fetch modules handle those two directions clearly.
This guide uses ad-hoc commands, so you can run one-off transfers without writing a full playbook. The same module arguments also work in playbooks when you want the task to be repeatable.
Prerequisites
Before executing the examples below, ensure you have the following:
- Ansible Control Node: A machine with Ansible installed.
- Inventory File: An operational inventory file (e.g.,
/etc/ansible/hosts) defining your managed nodes. - Connectivity: SSH key access configured to your remote hosts.
All examples will assume the target group is named webservers in the inventory.
Understanding Ad-Hoc Commands for File Transfer
Ad-hoc commands are single-line commands executed directly from the terminal, ideal for quick tasks that don't warrant a permanent playbook. The basic structure is:
ansible <host-group> -m <module-name> -a "key=value key2=value2 ..."
For file transfer, we use the -m copy or -m fetch module names, passing required arguments using the -a flag.
The copy Module: Pushing Files to Remote Nodes
The copy module is used to transfer a file located on the Ansible control machine to one or more managed nodes. This is the standard method for deploying configuration files, scripts, or small assets.
Key Arguments for copy
The copy module requires two essential arguments and accepts several optional arguments for configuration management:
| Argument | Description | Required? | Example Value |
|---|---|---|---|
src |
The local file path on the Ansible control machine. Absolute paths are clearest for ad-hoc commands. | Yes | /tmp/config.conf |
dest |
The path where the file will be placed on the remote managed node. | Yes | /etc/app/config.conf |
owner |
Name of the user that should own the file on the remote node. | No | nginx |
group |
Name of the group that should own the file on the remote node. | No | www-data |
mode |
Permissions (octal) to set on the destination file. | No | 0644 |
backup |
If yes, creates a backup file before overwriting the original. |
No | yes |
Example 1: Simple File Deployment
Suppose you have a custom Message of the Day (motd) file locally and want to push it to all webservers.
# Local file path: /home/user/ansible/motd_banner
# Remote destination: /etc/motd
ansible webservers -m copy -a "src=/home/user/ansible/motd_banner dest=/etc/motd"
Example 2: Setting Permissions and Ownership
If you are deploying a secure configuration file, you must specify the owner, group, and restricted permissions (e.g., only the owner can read/write).
# Deploying an application configuration file, owned by 'app_user', group 'devops',
# with read/write permissions only for the owner (0600).
ansible webservers -m copy -b -a "src=/tmp/app_settings.yaml dest=/etc/app/settings.yaml owner=app_user group=devops mode=0600"
Note on
-b: The-b(or--become) flag is necessary when the remote destination requires elevated permissions (like writing to/etc).
The fetch Module: Retrieving Files from Remote Nodes
The fetch module performs the inverse operation of copy: it retrieves a file from the managed node back to the Ansible control machine. This is useful for backing up configuration files, retrieving logs, or collecting diagnostic information.
Key Arguments for fetch
The fetch module requires the source file on the remote node and a destination directory on the control machine.
| Argument | Description | Required? | Example Value |
|---|---|---|---|
src |
The absolute path to the file on the remote managed node. | Yes | /var/log/nginx/error.log |
dest |
The absolute path to the directory on the control machine where the files will be saved. | Yes | /tmp/backups/logs |
flat |
If yes, the resulting filename will not contain the hostname structure (not recommended when fetching from multiple hosts). |
No | no (default) |
Critical Difference: Destination Structure
Unlike the copy module, the fetch module automatically creates a structured subdirectory path based on the remote hostname to prevent file naming collisions when retrieving files from multiple servers.
The resulting path on the control machine will look like this:
<dest>/<hostname>/<src>
For example, fetching /etc/nginx/nginx.conf from host1 to /tmp/backups results in:
/tmp/backups/host1/etc/nginx/nginx.conf
Example 3: Retrieving Remote Configuration Backups
To retrieve the running configuration file from all webservers to a local backup directory:
# Retrieve nginx.conf from all webservers to the local directory /tmp/config_backups
ansible webservers -m fetch -a "src=/etc/nginx/nginx.conf dest=/tmp/config_backups"
After running this command, if you targeted webserver1 and webserver2, your local directory structure would be:
/tmp/config_backups/
โโโ webserver1
โ โโโ etc
โ โโโ nginx
โ โโโ nginx.conf
โโโ webserver2
โโโ etc
โโโ nginx
โโโ nginx.conf
Example 4: Retrieving a Single File Without Host Structure (flat=yes)
If you are absolutely sure you are only fetching a file from a single host, or if you only need the file's content (not the origin structure), you can use flat=yes. This results in the file being placed directly in the destination folder, named after the original remote file.
# Retrieve a local health status report from a single host, saving it directly.
ansible webserver1 -m fetch -a "src=/tmp/health_status.txt dest=/tmp/reports flat=yes"
# Resulting path: /tmp/reports/health_status.txt
Warning: Only use
flat=yeswhen targeting a single host or if you intend to overwrite the file on subsequent runs, as Ansible will not prevent conflicts.
Best Practices and Security Considerations
Small file-transfer mistakes can become production incidents, especially when files land in /etc or contain secrets.
Always Set Permissions with copy
Never push a configuration file without explicitly defining the mode and owner. If you rely on the remote system's default umask, sensitive files (like SSH keys or database credentials) might end up with overly permissive access rights.
# Bad Practice (Mode derived from umask)
- name: Deploy insecure key
ansible.builtin.copy:
src: private.key
dest: /etc/app/private.key
# Good Practice (Explicitly restrict access)
- name: Deploy secure key
ansible.builtin.copy:
src: private.key
dest: /etc/app/private.key
mode: '0600'
owner: root
Use backup=yes for Critical Changes
When using copy to overwrite an existing, critical file (e.g., /etc/sudoers), include backup=yes. Ansible will create a timestamped backup copy on the remote node before overwriting the file, providing an easy rollback option.
Consider synchronize for Large Transfers
While copy and fetch work well for quick operations and small files, use ansible.posix.synchronize for large directory trees or efficient delta transfers. It wraps rsync, so the control node and target environment need the right rsync and SSH access.
Takeaway
Use copy when the source is on your control node and the destination is on the managed node. Use fetch when the source is on the managed node and you want the file saved locally.
| Module | Direction | Ad-Hoc Command Example |
|---|---|---|
copy |
Control Node -> Managed Node | ansible all -m copy -a "src=/local/file dest=/remote/path mode=0644" |
fetch |
Managed Node -> Control Node | ansible all -m fetch -a "src=/remote/file dest=/local/dir" |
For one server, flat=yes can make fetched files easier to read. For a group of servers, keep the default host-based directory structure so one host's log does not overwrite another's.