Beginner’s Guide to Jinja2 Templates and Filters in Ansible: Dynamic Configs Made Simple

Visual overview of using Jinja2 templates and filters in Ansible to automate configuration files

Welcome to this hands-on guide designed for Ansible learners who want to understand and apply Jinja2 templates effectively. This post explains:

  1. What Jinja2 is and what it can do (with clear examples)
  2. How to deploy dynamic configuration files like MOTD and SSHD using Jinja2
  3. How to use filters to process and clean up data
  4. Side-by-side examples showing template file, variable YAML file, and the deployed result

Let’s dive in and demystify Jinja2!

What is Jinja2 Template in Ansible?

Jinja2 is the templating engine that powers Ansible’s ability to dynamically generate files such as configuration files, scripts, or banners. Instead of writing one static file per server, we can write one smart template and customize its content depending on the host, group, or environment using variables.

In real-world scenarios, most configuration files are not 100% identical across machines:

  • Web servers may use one port, database servers another.
  • Hostnames, environment flags, allowed IPs, even login banners might differ.

Manually maintaining a different config file for every server is painful and error-prone. With Jinja2 templates, you only maintain a single template, and Ansible fills in the blanks depending on each host’s variable values.

This approach is:

  • Flexible: You can use conditions and loops
  • Reusable: One template can work for many different hosts
  • Safe: Less room for human error
  • Efficient: Easy to update, easy to read, easy to scale

In the next section, let’s explore what you can actually do with Jinja2 and how to use it through simple, real-life examples.

What Can Jinja2 Do? (With Examples)

Below are common tasks Jinja2 helps with — each paired with:

  • ✅ A Jinja2 Template snippet
  • 📄 A YAML variable file
  • 📥 The final result on the managed host

1. Injecting Variables into a File

Use case: Replace hardcoded values with dynamic variables.

✅ Jinja2 Template (nginx.conf.j2):

server_name {{ inventory_hostname }};
listen {{ nginx_port }};

📄 Variable File (host_vars/web01.yml):

nginx_port: 8080

📥 Deployed Output on web01:

server_name web01;
listen 8080;

2. Using If-Else Conditions

Use case: Customize config depending on environment.

✅ Jinja2 Template (sshd_config):

{% if env == 'staging' %}
PermitRootLogin yes
{% else %}
PermitRootLogin no
{% endif %}

📄 YAML:

env: production

📥 Output:

PermitRootLogin no

3. Looping Over a List

Use case: Add multiple IPs or users.

✅ Template (sshd_config):

{% for ip in allowed_ips %}
allow {{ ip }};
{% endfor %}

📄 YAML:

allowed_ips:
  - 10.0.0.1
  - 192.168.1.1

📥 Output:

allow 10.0.0.1;
allow 192.168.1.1;

4. Using Default Values

Use case: Fallback when a variable isn’t defined.

✅ Template (sshd_config):

Port {{ ssh_port | default(22) }}

📄 YAML (empty or missing ssh_port):

# ssh_port not defined

📥 Output:

Port 22

5. String Transformation

Use case: Format or sanitize variables.

✅ Template:

user = {{ fqdn | lower | replace('.example.com', '') }}

📄 YAML:

fqdn: WEB01.EXAMPLE.COM

📥 Output:

user = web01

🛠️ Real Example 1: Deploying Custom MOTD

🎯 Goal:

  • Create a login banner (motd) that shows each server’s hostname.
  • Web servers use SSH port 2900; DB servers use port 2600.

✅ Jinja2 Template (motd.j2): – Prepare Jinja2 template for motd config file display banner with hostname of managed host

✅ Jinja2 Template (sshd_config.jija): – Prepare Jinja2 template for sshd configuration file with port based on value of group variabels.

📄 Group Variables: – Define variable file include sshd_port value for webserver and dbserver group

🧾 Playbook:

-Writing playbook using module template to deploy Jinja2 templates and using handler to restart service sshd after change configuration port listening.

- name: Configure motd and ssh service for all host

  become: true

  hosts: all

  tasks:

    - name: Deploy motd file for all host

      template:

        src: motd.jija

        dest: /etc/motd

 

    - name: Backup sshd configuration file before change port

      shell: cp /etc/ssh/sshd_config /etc/ssh/sshd_config_bk2020

 

    - name: Deploy new sshd configurtaion file for change listen port

      template:

        src: sshd_config.jija

        dest: /etc/ssh/sshd_config

        mode: 600

        owner: root

        group: root

      notify:

       - restart sshd

  handlers:

    - name: restart sshd

      service:

        name: sshd

        state: restarted

Running runbook on Ansible Control node:

– Checking motd file and new ssh port listening on managed hosts

Filters in Jinja2 (Built-In Magic)

Jinja2 filters are powerful tools used to transform, reformat, or clean variable data before it’s rendered in your playbooks or templates. A filter does not modify the original variable, but instead returns a modified result on the fly.

🧠 Why Use Filters?

  • You may receive data in raw form (e.g. uppercase hostnames, duplicated list values, undefined variables) and need to clean or format it.
  • Filters help you avoid extra tasks or complex conditionals — simply apply them inline.
  • You can use them both in template files and within Ansible playbooks (e.g., debug, set_fact, etc.).

🔧 Where Do Filters Come From?

  • Some filters are built into Jinja2 language (e.g., default, upper, replace)
  • Others are available via Ansible plugins (e.g., ipaddr, to_yaml, json_query)

📚 You can find the full list of available filters and documentation here:

🔗 https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html

Real Example 3: Extracting IPs with ipaddr Filter

🎯 Goal: Using filter ipaddr to get valid IP address from a messy list

📄 Variable File (vars.yml):

🧾 Playbook:

- name: Get Valid IP Address From List Of Variables

  hosts: localhost

  vars_files:

   - list_of_ip

  tasks:

   - name: Valid IP Address

     debug:

       msg: "{{ mylist | ipaddr }}"

📥 Output:

– Or can using filter ipaddr to get list of IP Address with subnet mask anc convert to CIDR network and prefix as below example:

📄 Variable File (vars.yml):

🧾 Playbook:

- name: Get Valid IP Address From List Of Variables

  hosts: localhost

  vars_files:

   - list_of_ip

  tasks:

   - name: Valid IP Address

     debug:

       msg: "{{ mylist | ipaddr('network/prefix') }}"

📥 Output:

✨Summary

We’ve covered quite a bit — from understanding what Jinja2 is, to deploying custom config files like MOTD and SSHD, and even processing messy IP lists using filters. If you’ve made it this far, congrats! You’re no longer just copying nginx.conf from StackOverflow 😄

Here’s what to remember:

Jinja2 makes your config files dynamic and reusable, saving you from writing hundreds of near-identical files.
Variables define the “what”; templates define the “how” — and Jinja2 makes them work together.
Filters help clean, shape, or validate your data so you don’t have to manually adjust values in YAML or templates.
One playbook + one template = many smart, customized deployments. That’s the real power of automation.

Whether you’re managing 3 servers or 3,000, this approach scales, simplifies, and — let’s face it — feels a bit magical

💡 Tip from TechNoStress:

If you’re ever repeating yourself more than twice in a playbook or config file, it’s probably time to reach for a Jinja2 template. Your future self will thank you.

📣 You Might Also Like

If you enjoyed this guide, you may also find this related article useful:
🔗 Automate User Account Inventory Across Linux Servers Using Ansible
It walks you through automating user audits across multiple servers using Ansible — perfect for combining shell scripting and configuration management.

5 thoughts on “Beginner’s Guide to Jinja2 Templates and Filters in Ansible: Dynamic Configs Made Simple”

Leave a Comment

Your email address will not be published. Required fields are marked *