The Error
You kick off an Ansible playbook and it dies immediately with:
fatal: [host]: FAILED! => {"changed": false, "msg": "Could not find or access 'templates/nginx.conf.j2' on the Ansible Controller."}
Nine times out of ten, the task using ansible.builtin.template (or the shorthand template) is the culprit. Notice the wording: Ansible searched on the controller โ the machine where you ran ansible-playbook โ not the remote host. The file simply wasn't where Ansible expected it.
Why This Happens
Ansible already knows to look inside a templates/ subdirectory relative to your playbook or role. That's built into the lookup logic. So src should contain just the filename โ nginx.conf.j2, not templates/nginx.conf.j2.
Two things cause this error almost every time:
- You wrote
src: templates/nginx.conf.j2, so Ansible looks fortemplates/templates/nginx.conf.j2. That path doesn't exist. - The
.j2file is in the wrong directory โ not where Ansible actually searches.
Step-by-Step Fix
Step 1 โ Check How You're Calling the Template Module
Look at the src field in your task:
# WRONG โ don't include the "templates/" prefix
- name: Deploy nginx config
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
# CORRECT โ just the filename
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
Ansible already searches templates/ next to your playbook or inside your role. Adding the prefix doubles the path to something like templates/templates/nginx.conf.j2 โ which never exists.
Step 2 โ Confirm the File Exists in the Right Place
Scenario A: Playbook-level template (no roles)
The template must live in a templates/ directory right next to your playbook:
site.yml
templates/
nginx.conf.j2 โ must be here
Quick check:
ls -la templates/nginx.conf.j2
Scenario B: Inside a role
Tasks inside a role look in that role's own templates/ folder โ not the project root:
roles/
nginx/
tasks/
main.yml
templates/
nginx.conf.j2 โ must be here
Quick check:
ls -la roles/nginx/templates/nginx.conf.j2
The classic mistake: you drop the template in the project root's templates/ but run the task from inside a role. Ansible searches the role's directory first and never finds it there.
Step 3 โ Fix the Directory Structure
Missing directory or wrong location? Create it and move the file:
# For a role named "nginx"
mkdir -p roles/nginx/templates
mv nginx.conf.j2 roles/nginx/templates/
# For a playbook-level template
mkdir -p templates
mv nginx.conf.j2 templates/
Step 4 โ Check for Typos and Case Sensitivity
Linux filesystems are case-sensitive. Nginx.conf.j2 and nginx.conf.j2 are two completely different files. Confirm the exact filename on disk:
ls templates/
# Should show: nginx.conf.j2 (not Nginx.conf.j2)
Step 5 โ Using an Absolute Path (Last Resort)
Got a template stored in a non-standard location? Pass the full path directly:
- name: Deploy nginx config
ansible.builtin.template:
src: /opt/configs/nginx.conf.j2
dest: /etc/nginx/nginx.conf
It works, but it ties the playbook to a specific machine. Avoid this in anything you plan to share or reuse.
Verify the Fix
Crank up verbosity to watch exactly which path Ansible resolves:
ansible-playbook site.yml -vvv 2>&1 | grep -i "template\|j2"
When it works, you'll see something like:
TASK [Deploy nginx config] ****
ok: [host] => changed=true
Want to test without touching any files? Add --check:
ansible-playbook site.yml --check
Quick Lookup: Where Ansible Searches for Templates
# Playbook-level (site.yml at project root)
./templates/
./
# Role-level (task inside roles/nginx/tasks/main.yml)
roles/nginx/templates/
roles/nginx/
./templates/ โ also checked, but role templates take priority
./
Ansible works down this list and uses the first match it finds. The templates/ directory is already part of the search โ that's exactly why prefixing src with it causes the double-path lookup failure.
Tips
- Never prefix
srcwithtemplates/โ this is the #1 cause of this error, and it's easy to miss on a quick glance. - Moving tasks from a flat playbook into a role? Move the templates into the role's
templates/directory at the same time, or they'll fall outside Ansible's search path. - Subdirectories inside
templates/work fine insrc:src: nginx/nginx.conf.j2resolves totemplates/nginx/nginx.conf.j2. - Run
ansible-playbook --list-tasksto confirm which role owns a task before you start hunting for the template path.

