The Problem Scenario
You are deploying a configuration file to a dozen web servers. You trigger your playbook, expecting a clean row of green 'changed' statuses. Instead, the process grinds to a halt with a blunt red error message:
fatal: [web_server]: FAILED! => {"changed": false, "msg": "could not find src=/etc/configs/nginx.conf"}
The confusion starts when you run ls -l /etc/configs/nginx.conf on your machine and see the file sitting right there. This error rarely means the file is missing from your disk. Usually, it means Ansible is looking in a different place than you intended due to how it resolves relative paths.
Why This Error Happens
The copy module transfers files from your control node (the laptop or CI/CD runner where you type the command) to the remote host. If the Ansible process cannot find the file locally, it cannot upload it. This failure typically stems from one of four issues:
- Path Misalignment: You executed
ansible-playbookfrom~/projects/, but your source path is relative to~/projects/ansible/playbooks/. - Role Hierarchy Breaches: You placed a file in a folder named
config/instead of the standardfiles/directory within an Ansible role. - Variable Failures: A variable like
{{ config_path }}is undefined or contains a typo, resulting in a null or broken path. - Permission Barriers: The local user executing Ansible lacks read permissions for the source file, making it "invisible" to the module.
Quick Fixes
1. Test with an Absolute Path
Use an absolute path as a diagnostic step. If the task succeeds with a full path, you have confirmed a relative pathing issue. For example, change your src to /home/dev/deploy/configs/app.conf. If this works, you know the file is accessible; only your mapping was off.
- name: Diagnostic copy using absolute path
ansible.builtin.copy:
src: /home/user/project/configs/app.conf
dest: /etc/app/app.conf
2. Align with the Playbook Directory
By default, Ansible looks for files relative to the .yml file you are running. If your directory structure looks like the example below, your src should simply point to the folder sitting next to your playbook.
# Directory structure:
# ├── site.yml
# └── files/
# └── app.conf
- name: Copy app configuration
ansible.builtin.copy:
src: files/app.conf
dest: /etc/app/app.conf
Permanent Fixes and Best Practices
Leverage the Role Directory Structure
Roles provide a built-in search logic that simplifies your tasks. When a task is inside roles/webserver/tasks/main.yml, Ansible automatically scans roles/webserver/files/ for any source files. You can omit the path entirely.
Standard Role Structure:
roles/
common/
tasks/main.yml
files/ntp.conf
The Task in main.yml:
- name: Deploy NTP config
ansible.builtin.copy:
src: ntp.conf # Ansible finds this automatically in ../files/
dest: /etc/ntp.conf
Anchor Paths with playbook_dir
Complex projects often involve running playbooks from different subfolders. To keep your paths portable, use the playbook_dir magic variable. This ensures the path is always calculated from the root of your playbook folder, regardless of where you initiate the command.
- name: Copy script from project root
ansible.builtin.copy:
src: "{{ playbook_dir }}/scripts/setup.sh"
dest: /usr/local/bin/setup.sh
mode: '0755'
Debug the Local Controller
If the error persists, force Ansible to report what it sees on your local machine. Use the stat module and delegate_to: localhost to verify the file's existence before the copy attempt. This is the fastest way to catch permission issues or empty variables.
- name: Verify local file existence
ansible.builtin.stat:
path: "{{ playbook_dir }}/files/my-config.conf"
register: local_file
delegate_to: localhost
- name: Fail if file is missing
ansible.builtin.fail:
msg: "The file was not found on the control node!"
when: not local_file.stat.exists
Verification Steps
After applying a fix, verify the solution with these three steps:
- Increase Verbosity: Run
ansible-playbook -vv. The output will reveal the exact absolute path Ansible is trying to open on your local disk. - Perform a Dry Run: Execute with
--check. If the 'could not find src' error is gone, Ansible has successfully mapped the local file and is ready to push it. - Confirm Permissions: Ensure the local file has at least
644permissions (-rw-r--r--) so the Ansible user can read it.
Using a standard role structure or anchoring paths with {{ playbook_dir }} eliminates 90% of these errors. These habits make your automation predictable and portable across different team environments.

