Ansible NGINX Role Creation
This lesson walks through building a production-quality, fully reusable Nginx role from scratch. Rather than showing you a finished product, it walks you through the decisions made at each step — why a variable goes in defaults vs vars, how to structure tasks for readability, and how to make the role work across Ubuntu and CentOS without duplication. The result is a role you can publish, share, and reuse across every project that needs Nginx.
Step 1: Initialise the Role
ansible-galaxy role init nginx cd roles/nginx
Step 2: Define the Public Interface in defaults/main.yml
Every value an operator might want to customise lives here. Write a comment for each variable:
---
# nginx defaults
# Package name (differs between OS families)
nginx_package_name: nginx
# Service name
nginx_service_name: nginx
# User and group that Nginx runs as
nginx_user: www-data
nginx_group: www-data
# Listen port for the default virtual host
nginx_http_port: 80
nginx_https_port: 443
# Document root for the default virtual host
nginx_document_root: /var/www/html
# Enable or disable the default virtual host
nginx_remove_default_vhost: true
# List of virtual hosts to configure
# Each item: { server_name, document_root, port, template (optional) }
nginx_vhosts: []
# Worker configuration
nginx_worker_processes: auto
nginx_worker_connections: 1024
# Logging
nginx_access_log: /var/log/nginx/access.log
nginx_error_log: /var/log/nginx/error.log
nginx_log_format: mainStep 3: Define Internal Variables in vars/main.yml
---
# OS-specific package names (not intended for operator override)
_nginx_packages:
Debian:
- nginx
RedHat:
- nginx
- epel-release
# OS-specific config paths
_nginx_conf_dir:
Debian: /etc/nginx/sites-available
RedHat: /etc/nginx/conf.d
_nginx_conf_enabled:
Debian: /etc/nginx/sites-enabled
RedHat: ~ # RedHat uses conf.d directly, no sites-enabledStep 4: Write tasks/main.yml
Use import_tasks to split tasks by concern — keeps main.yml readable:
---
- name: Include OS-specific variables
include_vars: "{{ ansible_os_family }}.yml"
failed_when: false # Graceful failure if OS file doesn't exist
- import_tasks: install.yml
tags: [nginx, install]
- import_tasks: configure.yml
tags: [nginx, configure]
- import_tasks: vhosts.yml
tags: [nginx, vhosts]
- import_tasks: service.yml
tags: [nginx, service]Step 5: Write tasks/install.yml
---
- name: Install Nginx (Debian/Ubuntu)
apt:
name: "{{ nginx_package_name }}"
state: present
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install EPEL and Nginx (RedHat/CentOS)
yum:
name: "{{ item }}"
state: present
loop: "{{ _nginx_packages['RedHat'] }}"
when: ansible_os_family == "RedHat"Step 6: Write tasks/configure.yml
---
- name: Deploy nginx.conf
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: nginx -t -c %s
notify: Reload Nginx
- name: Remove default vhost (Debian)
file:
path: /etc/nginx/sites-enabled/default
state: absent
when:
- ansible_os_family == "Debian"
- nginx_remove_default_vhost | bool
notify: Reload NginxStep 7: Write tasks/vhosts.yml
---
- name: Deploy virtual host configurations
template:
src: "{{ item.template | default('vhost.conf.j2') }}"
dest: "{{ _nginx_conf_dir[ansible_os_family] }}/{{ item.server_name }}.conf"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_vhosts }}"
notify: Reload Nginx
- name: Enable virtual hosts (Debian only)
file:
src: "{{ _nginx_conf_dir['Debian'] }}/{{ item.server_name }}.conf"
dest: "/etc/nginx/sites-enabled/{{ item.server_name }}.conf"
state: link
loop: "{{ nginx_vhosts }}"
when: ansible_os_family == "Debian"
notify: Reload Nginx
- name: Ensure document roots exist
file:
path: "{{ item.document_root }}"
state: directory
owner: "{{ nginx_user }}"
group: "{{ nginx_group }}"
mode: '0755'
loop: "{{ nginx_vhosts }}"Step 8: Write tasks/service.yml
---
- name: Ensure Nginx is started and enabled
service:
name: "{{ nginx_service_name }}"
state: started
enabled: trueStep 9: Write handlers/main.yml
---
- name: Reload Nginx
service:
name: "{{ nginx_service_name }}"
state: reloaded
- name: Restart Nginx
service:
name: "{{ nginx_service_name }}"
state: restartedStep 10: Write the nginx.conf.j2 Template
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log {{ nginx_access_log }};
error_log {{ nginx_error_log }};
gzip on;
include /etc/nginx/conf.d/*.conf;
{% if ansible_os_family == "Debian" %}
include /etc/nginx/sites-enabled/*;
{% endif %}
}Step 11: Write the vhost.conf.j2 Template
server {
listen {{ item.port | default(nginx_http_port) }};
server_name {{ item.server_name }};
root {{ item.document_root }};
index index.html index.htm;
access_log /var/log/nginx/{{ item.server_name }}-access.log;
error_log /var/log/nginx/{{ item.server_name }}-error.log;
location / {
try_files $uri $uri/ =404;
}
}Step 12: Write meta/main.yml
---
galaxy_info:
author: yourname
description: Install and configure Nginx web server
license: MIT
min_ansible_version: "2.9"
platforms:
- name: Ubuntu
versions: [focal, jammy]
- name: EL
versions: [7, 8, 9]
galaxy_tags: [web, nginx, http]
dependencies: []Using the Role in a Playbook
---
- name: Configure web servers
hosts: webservers
become: true
roles:
- role: nginx
vars:
nginx_http_port: 8080
nginx_vhosts:
- server_name: myapp.example.com
document_root: /var/www/myapp
port: 8080Try This: Add SSL Support
Extend the role by adding a vhost-ssl.conf.j2 template that includes SSL directives. Add nginx_ssl_certificate and nginx_ssl_certificate_key defaults. Update vhosts.yml to deploy the SSL template when item.ssl is true. Test by adding an SSL virtual host entry to the nginx_vhosts list.
Summary
A production-quality Nginx role separates concerns across install, configure, vhosts, and service task files. Defaults expose every tunable parameter with sensible values. OS-specific logic uses ansible_os_family conditionals. The validate parameter on the template module prevents deploying broken configs. Vhost configuration loops over a list defined by the operator, keeping the role generic and reusable across any number of projects.
