Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Ansible By James Joyner IV · · 9 min read

Ansible Error Guide: 'Destination directory does not exist' copy/template Failure

Fix Ansible's copy/template 'Destination directory ... does not exist' error: diagnose missing parent paths, wrong dest, file vs dir confusion, and permission issues.

  • #ansible
  • #troubleshooting
  • #errors
  • #files

Exact Error Message

fatal: [app-01]: FAILED! => {
    "changed": false,
    "checksum": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
    "msg": "Destination directory /etc/myapp/conf.d does not exist"
}

The same class of error from the template and copy modules can also read Destination /opt/app/releases/current not writable or dst path /etc/myapp/app.conf does not exist, but the most common form is the missing parent directory shown above.

What the Error Means

The copy, template, and unarchive modules write a file to a dest: path. They will create the destination file, but they do not, by default, create the parent directories leading up to it. If the directory that should contain the file does not exist on the remote host, the module fails with Destination directory ... does not exist.

In other words, Ansible found a valid source and a valid file name but had nowhere to put it because the containing folder is absent.

Common Causes

  • The parent directory simply was not created before the copy/template task ran (no preceding file: state=directory task).
  • A role assumed a package created the directory (e.g. /etc/nginx/conf.d) but the package is not installed yet.
  • dest: ends without a trailing slash where a directory was intended, or has a typo in the path.
  • Task ordering: the directory-creating task runs later in the play, or in a role that has not been applied.
  • The directory exists but the become user cannot traverse into it (surfacing as not writable rather than not existing).
  • A templated dest: resolved to an unexpected path because a variable was empty or wrong.

How to Reproduce the Error

Copy a file into a directory that has not been created:

- hosts: app
  become: true
  tasks:
    - name: Deploy app config
      ansible.builtin.template:
        src: app.conf.j2
        dest: /etc/myapp/conf.d/app.conf
        owner: root
        mode: "0644"
ansible-playbook deploy.yml -i inventory.ini --check --diff -vvv
fatal: [app-01]: FAILED! => {"changed": false, "msg": "Destination directory /etc/myapp/conf.d does not exist"}

Diagnostic Commands

Run the play in check mode with verbosity to see the exact resolved destination:

ansible-playbook deploy.yml -i inventory.ini --check --diff -vvv

Confirm what the destination path actually resolves to (in case it is templated):

ansible app-01 -i inventory.ini -m debug -a "msg={{ '/etc/myapp/conf.d/app.conf' }}"

Check whether the parent directory exists and who owns it on the host:

ansible app-01 -i inventory.ini -b -m command -a "ls -ld /etc/myapp /etc/myapp/conf.d"

Verify the path with the stat module to distinguish “missing” from “not a directory”:

ansible app-01 -i inventory.ini -b -m stat -a "path=/etc/myapp/conf.d"

Step-by-Step Resolution

  1. Read the resolved destination from the error. Confirm it is the path you actually intended and not the result of an empty variable.

  2. Create the parent directory first. Add an explicit file task before the copy/template:

- name: Ensure config directory exists
  ansible.builtin.file:
    path: /etc/myapp/conf.d
    state: directory
    owner: root
    mode: "0755"

- name: Deploy app config
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/myapp/conf.d/app.conf
    mode: "0644"
  1. Fix task ordering. If another role creates the directory, make this role depend on it or move the directory task earlier.

  2. Check for trailing-slash and typo mistakes. dest: /etc/myapp/conf.d/ (with a slash) treats the target as a directory and copies the source file into it; without it the last segment is the file name. Use the form that matches your intent.

  3. If the directory exists but is “not writable,” fix ownership/permissions or ensure become: true runs as a user that can write there.

  4. Re-run in check mode, then apply:

ansible-playbook deploy.yml -i inventory.ini --check --diff
changed: [app-01]

Prevention and Best Practices

  • Always pair a file write with an explicit file: state=directory task that ensures the parent path exists; do not rely on a package or earlier role to have made it.
  • Keep directory-creation tasks at the top of a role’s task file so every later file write has a guaranteed home.
  • Guard templated dest: paths with | default(...) so an empty variable cannot collapse the path to something unintended.
  • Be deliberate about trailing slashes in dest: to avoid file-vs-directory confusion.
  • Run roles in check mode in CI; a missing directory shows up immediately without changing hosts.
  • Use mode and owner consistently so a directory that exists but is unwritable does not turn into a confusing “not writable” failure later.
  • Destination ... not writable — the directory exists but the become user lacks permission.
  • dst path ... not found from unarchive — the extraction target directory is missing.
  • Could not find or access '...' on the Ansible Controller — a missing source file, not a missing destination.
  • failed to transfer file to ... — an SSH/SFTP transport problem rather than a missing directory.

Frequently Asked Questions

Why does Ansible not just create the directory for me? The copy/template modules create the destination file but intentionally do not create arbitrary parent directories, to avoid silently making paths in the wrong place. Create parents explicitly with the file module.

My dest path looks wrong in the error. It is probably templated from a variable that was empty or mistyped. Print it with debug and guard it with | default(...).

Trailing slash or not? With a trailing slash, dest: is treated as a directory and the source file is placed inside it. Without it, the final path segment is the file name. Pick the one matching your intent.

It says the directory does not exist but I can see it. Run stat on the path: it may be a file, a broken symlink, or unreadable by the become user, which Ansible reports differently. For more file and templating patterns, see the Ansible guides.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.