Free tech learning for everyone, from basics to pro

AnsibleProgramming and Application SecurityIntermediate

Templates with Jinja2 in Ansible

In short: Jinja2 templates separate the shape of a configuration from the data that fills it. Ansible renders a template for each device using its variables, so one template can produce hundreds of consistent, reviewable configurations.

Why templates

Twenty access switches have almost identical configurations: only the hostname, management IP and a few VLANs differ. Copying and editing text by hand leads to typos and drift. A template holds the common structure once, with placeholders for what changes. Ansible uses the Jinja2 templating language, the same one used by many Python tools, to fill in those placeholders from inventory variables.

From data to device configVariablesgroup/hostvarsTemplate.j2 fileRenderJinja2 engineConfig textper deviceDeployconfig module
Figure 1. Jinja2 combines a template with each device's variables to produce its own configuration.

A switch template

Jinja2 essentials{{ var }}insert a value{% for %}loop over lists{% if %}conditional blocksFiltersvalue | upperDefaultsvar | default(x)Whitespacetrim_blocks, -%}
Figure 2. A few Jinja2 features cover most network configuration templates.
{# templates/access_switch.j2 #}
hostname {{ inventory_hostname }}
!
{% for server in ntp_servers %}
ntp server {{ server }}
{% endfor %}
logging host {{ syslog_server }}
!
{% for vlan in vlans %}
vlan {{ vlan.id }}
 name {{ vlan.name | upper }}
{% endfor %}
!
interface Vlan{{ mgmt_vlan | default(99) }}
 ip address {{ mgmt_ip }} {{ mgmt_mask }}
{% if enable_ssh_v2 | default(true) %}
ip ssh version 2
{% endif %}
  • {{ ... }} prints a value; {% ... %} is logic; {# ... #} is a comment that never appears in the output.
  • Filters transform values: upper, default, join, replace, and Ansible's ansible.utils.ipaddr family for IP maths.
  • Ansible sets trim_blocks by default, so the line breaks after {% %} tags are removed. Use {%- or -%} to strip extra whitespace when needed.

Rendering and deploying

Render to files first, review them, then deploy. This separates "is the generated config right?" from "did the push work?".

- name: Build and deploy access switch configs
  hosts: access
  gather_facts: false
  tasks:
    - name: Render config locally for review
      ansible.builtin.template:
        src: templates/access_switch.j2
        dest: "build/{{ inventory_hostname }}.cfg"
      delegate_to: localhost

    - name: Push rendered config
      cisco.ios.ios_config:
        src: "build/{{ inventory_hostname }}.cfg"
        save_when: modified
      tags: deploy

delegate_to: localhost runs the template task on the control node, since the switch cannot store files itself. Run the playbook once with --skip-tags deploy to build files only, check them with a diff or a colleague, then run again with --tags deploy. For Linux servers, the same template module writes the file directly to the server, for example a chrony.conf or an Nginx site.

Good template habits

HabitWhy
Keep logic lightComplex calculations belong in variables or filters, not deep nested ifs
Use default() carefullySafe defaults are useful; hidden defaults surprise people
Set DEFAULT_UNDEFINED_VAR_BEHAVIOR to failAnsible already errors on undefined variables; do not switch this off
One template per role or platformEasier to read than one huge template full of vendor checks
Store templates in GitChanges are reviewed and history is kept

Tip: Jinja2 is not only for Ansible. The same skills apply when you generate configs from Python with the jinja2 library, as covered in Jinja2 for network configuration.

Practice

What is the difference between {{ }} and {% %} in Jinja2?
{{ }} outputs the value of an expression; {% %} runs logic such as loops and conditions without printing anything itself.
Why use delegate_to: localhost with the template module for switches?
The rendered file needs to be written on the control node because the switch is not a Linux host that Ansible can copy files to.
Why render configs to files before deploying?
It lets you review and diff the generated text, catching data or template errors before they reach devices.

Key takeaways

  • Templates hold the shared structure; variables hold per-device data.
  • {{ }} prints, {% %} controls logic, {# #} comments.
  • Filters such as default, upper and ipaddr transform values.
  • Render to files, review, then deploy with a config module.
  • Keep templates simple and version controlled.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.