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: main

Step 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-enabled

Step 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 Nginx

Step 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: true

Step 9: Write handlers/main.yml

---
- name: Reload Nginx
  service:
    name: "{{ nginx_service_name }}"
    state: reloaded

- name: Restart Nginx
  service:
    name: "{{ nginx_service_name }}"
    state: restarted

Step 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: 8080

Try 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.

Leave a Comment