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:

  1. Ansible Control Node: A machine with Ansible installed.
  2. Inventory File: An operational inventory file (e.g., /etc/ansible/hosts) defining your managed nodes.
  3. 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=yes when 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.