Ansible variables, templates and handlers: configure an nginx site
Use group variables, Jinja2 templates and handlers together: render an nginx server block from variables, test the config and reload nginx only when the file really changed.
Real playbooks avoid hard-coded values. You keep settings in variables, generate config files from templates, and restart or reload services through handlers so they are touched only when something changed. This example sets up a small site on port 8080 on Debian or Ubuntu hosts in an inventory group called web.
Project layout
project/
inventory.ini
hello.yml
group_vars/
web.yml
templates/
index.html.j2
site.conf.j2
Variables: group_vars/web.yml
Files in group_vars/ named after an inventory group are loaded automatically for every host in that group. group_vars/all.yml would apply to all hosts.
---
site_name: hello
site_root: /var/www/hello
nginx_port: 8080
greeting: "Hello from Ansible"
Variables can also live in the play (vars:), in files loaded with vars_files:, in host_vars/HOSTNAME.yml, or on the command line with -e. When the same name is defined in several places, precedence decides; extra vars from -e always win. Keep each variable in one place and you will rarely need to think about it.
Templates: templates/*.j2
Templates use Jinja2. {{ name }} inserts a variable. Rendering happens on the control node, and the finished file is copied to the host. Facts gathered from the host, such as ansible_facts['hostname'], are available too.
<!doctype html>
<title>{{ site_name }}</title>
<h1>{{ greeting }}</h1>
<p>Served by {{ ansible_facts['hostname'] }} on port {{ nginx_port }}.</p>
That is templates/index.html.j2. Next, templates/site.conf.j2:
# Managed by Ansible. Local changes will be overwritten.
server {
listen {{ nginx_port }};
server_name {{ inventory_hostname }};
root {{ site_root }};
index index.html;
}
The playbook: hello.yml
---
- name: Configure the hello site
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Create the site folder
ansible.builtin.file:
path: "{{ site_root }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Render the home page
ansible.builtin.template:
src: templates/index.html.j2
dest: "{{ site_root }}/index.html"
owner: root
group: root
mode: "0644"
- name: Render the nginx server block
ansible.builtin.template:
src: templates/site.conf.j2
dest: "/etc/nginx/conf.d/{{ site_name }}.conf"
owner: root
group: root
mode: "0644"
notify: nginx config changed
- name: Make sure nginx is running and starts at boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Test nginx configuration
ansible.builtin.command: nginx -t
changed_when: false
listen: nginx config changed
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
listen: nginx config changed
In YAML, a value that starts with {{ must be quoted, as in path: "{{ site_root }}". Without quotes YAML reads the braces as a dictionary and the playbook fails to parse.
How the handlers work
- The server-block task has
notify: nginx config changed. It notifies only when the template task reports changed, that is, when the rendered file differs from what is on the host. - Both handlers
listento that topic, so one notification triggers both. Handlers run in the order they are written in thehandlerssection: firstnginx -ttests the configuration, then nginx reloads. If the test fails, the play stops for that host before the reload. - Notified handlers run once, after all tasks of the play, even if several tasks notified them. To run them earlier, add a task
- ansible.builtin.meta: flush_handlers. changed_when: falseon the test task tells Ansible thatnginx -tnever changes anything, so it does not show up as a change.
Run it
ansible-playbook -i inventory.ini hello.yml --check --diff
ansible-playbook -i inventory.ini hello.yml
curl http://192.0.2.10:8080/
--diff shows the lines a template would change. Edit greeting and run again: only the home page task reports a change and no reload happens. Change nginx_port and the server block changes, so the handlers test and reload nginx. Remember to allow the new port in any firewall on the hosts.
Override a value for one run with an extra var, which beats every other source: ansible-playbook -i inventory.ini hello.yml -e nginx_port=8081.
Official documentation
See also
- 0181 Ansible concepts: inventory, modules, playbooks, idempotency and roles Ansible configures many servers over SSH from one control machine. Learn the core pieces: inventory, modules, tasks, playbooks, roles, and why runs… Beginner · Concept · Linux, macOS Article Ansible 15 Oct 2026
- 0182 Your first Ansible playbook: install nginx and publish a page A complete beginner playbook that installs nginx on Debian or Ubuntu servers, starts it at boot and deploys a home page, with… Beginner · How-to · Linux, macOS Script Ansible 15 Oct 2026
- 0184 Ansible Vault: keep passwords and keys encrypted in your repo Ansible Vault encrypts variable files or single values so secrets can live in Git next to your playbooks. Create, edit and rekey… Advanced · How-to · Linux, macOS Article Ansible 15 Oct 2026
- 0185 Ansible ad-hoc commands cheat sheet One-line ansible commands for quick jobs across many servers: ping hosts, run a command, copy files, manage packages, users and services, gather… Intermediate · Reference · Linux, macOS Article Ansible 15 Oct 2026
- 0186 Fix: Ansible UNREACHABLE! Failed to connect to the host via ssh Ansible could not open an SSH session to the host, so no task ran. The text after the colon tells you why:… Intermediate · Fix · Linux, macOS Article Ansible · SSH & servers 15 Oct 2026
- 0254 Automate three servers with Ansible: roles, templates, Vault and safe dry runs Build a real Ansible project for two web servers and one database server: inventory groups, SSH keys, roles, templates, handlers, an encrypted… Intermediate · Tutorial · Linux, macOS Article Ansible · Keys & config 15 Oct 2026
Repos in the wild
- Ansible: agentless IT automation ansible/ansible The source repository of ansible-core, the engine behind Ansible playbooks, ad-hoc commands and the ansible-galaxy and ansible-vault tools. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +4,824 chars