Ansible Error Guide: 'template error while templating string' Jinja2 Failure
Fix Ansible's 'template error while templating string' Jinja2 error: diagnose syntax mistakes, undefined filters, type errors, and bad expressions inside templates and vars.
- #ansible
- #troubleshooting
- #errors
- #jinja2
Exact Error Message
fatal: [app-01]: FAILED! => {
"changed": false,
"msg": "AnsibleError: template error while templating string: expected token 'end of print statement', got '='. String: {{ user_count = 5 }}"
}
The headline is always template error while templating string:, followed by the specific Jinja2 complaint and the offending string. Common tails include expected token ..., unexpected '}', No filter named '...', and expected name or number.
What the Error Means
Ansible evaluates Jinja2 expressions (anything inside {{ ... }} or {% ... %}) when it renders templates, conditionals, and variable values. When the Jinja2 engine cannot parse or evaluate one of those expressions, it raises a templating error and Ansible surfaces it as template error while templating string.
This is a parse/evaluation failure inside the expression itself — different from AnsibleUndefinedVariable, which means a variable was referenced but never defined. Here the engine got far enough to try to compile your expression and choked on its syntax or an operation it could not perform.
Common Causes
- Using
=for assignment inside{{ }}instead of comparison==, e.g.{{ x = 5 }}. - A typo’d or non-existent filter, e.g.
{{ value | defaul('x') }}or a filter from a collection that is not installed. - Mismatched or doubled braces:
{{{ var }}}or a missing closing}}. - Calling a string method or filter on the wrong type (passing a list where a string is expected).
- Nesting
{{ }}inside{{ }}(you almost never need the inner braces). - A raw Jinja2 control character (
{,},%) in a.j2template that was meant as literal text.
How to Reproduce the Error
Create a template with an invalid expression and apply it:
# templates/app.conf.j2
max_users = {{ user_count = 5 }}
ansible-playbook deploy.yml -i inventory.ini --check --diff -vvv
fatal: [app-01]: FAILED! => {"changed": false, "msg": "AnsibleError: template error while templating string: expected token 'end of print statement', got '='. String: max_users = {{ user_count = 5 }}"}
A missing-filter variant looks like this:
fatal: [app-01]: FAILED! => {"msg": "template error while templating string: No filter named 'defaul'. String: {{ region | defaul('us-east-1') }}"}
Diagnostic Commands
Run the play in check mode with full verbosity to capture the failing string verbatim:
ansible-playbook deploy.yml -i inventory.ini --check --diff -vvv
Render a suspect template in isolation against a real host’s facts using the template lookup in a debug task, or test an expression directly:
ansible app-01 -i inventory.ini -m debug -a "msg={{ region | default('us-east-1') }}"
Confirm whether a filter comes from a collection that is installed:
ansible-doc -t filter -l | grep -i community
Inspect the variables feeding the template so you know their real types:
ansible app-01 -i inventory.ini -m setup -a "filter=ansible_default_ipv4"
Step-by-Step Resolution
-
Read the
String:portion of the error. Ansible prints the exact expression that failed — start there rather than guessing which line. -
Fix the operator. Inside
{{ }}you compare, you do not assign. Replace=with==for comparison, or move the assignment intoset_fact/vars:
- set_fact:
user_count: 5
-
Correct filter names.
defaulshould bedefault,to_jsonnottojsonin some contexts. Verify withansible-doc -t filter <name>. -
Balance the braces. Use exactly
{{ expr }}for output and{% stmt %}for logic. Remove any stray inner{{ }}. -
Match types. If a filter expects a string but receives a list, convert it first (
{{ items | join(',') }}), or guard withdefault:
{{ (region | default('us-east-1')) }}
- Escape literal Jinja2 in templates. If a
.j2file must contain literal{{or%, wrap it:
{% raw %}{{ this stays literal }}{% endraw %}
- Re-run in check mode and confirm the template renders cleanly before applying for real:
ansible-playbook deploy.yml -i inventory.ini --check --diff
Prevention and Best Practices
- Keep expressions simple. Push complex logic into
set_factor precomputed variables rather than cramming it into one template line. - Always guard optional variables with
| default(...)so a missing value degrades gracefully instead of erroring. - Lint templates.
ansible-lintcatches many bad expressions before runtime. - Test templates against real facts in check mode in CI, not just on your laptop where variables happen to be defined.
- Pin and install the collections that provide your filters; a
No filter namederror often just meanscommunity.generalis missing. - Use
{% raw %}blocks for config files that legitimately contain{{(for example, other templating engines’ syntax).
Related Errors
AnsibleUndefinedVariable: '...' is undefined— the variable does not exist, as opposed to a syntax error in the expression.The conditional check '...' failed— awhen:expression that templated but evaluated to an error.No filter named '...'— a specific sub-case usually caused by a missing collection.Unexpected templating type error— a type mismatch (for example, applying a string filter to a dict).
Frequently Asked Questions
What is the difference between this and AnsibleUndefinedVariable? This error is a parse/evaluation failure in the expression itself. AnsibleUndefinedVariable means the syntax was fine but a referenced variable has no value.
Why does {{ x = 5 }} fail? Jinja2 print statements ({{ }}) only output expressions; they cannot assign. Use set_fact or vars, and use == for comparison.
My filter exists in docs but Ansible says No filter named. The filter likely lives in a collection (e.g. community.general) that is not installed. Install it with ansible-galaxy collection install and verify with ansible-doc -t filter.
How do I keep literal braces in a template? Wrap the section in {% raw %} ... {% endraw %} so Jinja2 emits it verbatim. For more Jinja2 and variable patterns, see the Ansible guides.
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.