AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Nginx: Install, Configure & Automate (Complete Guide)

By Luca Berton · Published 2026-04-03 · Category: installation

Complete guide to Ansible Nginx automation. Install Nginx, manage virtual hosts, SSL certificates, reverse proxy, and load balancing with examples.

Nginx is the most popular web server and reverse proxy — and Ansible makes it easy to automate installation, configuration, and deployment across your infrastructure.

Install Nginx

Ubuntu/Debian

- name: Install Nginx on Ubuntu
  hosts: webservers
  become: true
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Ensure nginx is running
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

RHEL/CentOS/AlmaLinux

- name: Install Nginx on RHEL
  hosts: webservers
  become: true
  tasks:
    - name: Install EPEL repository
      ansible.builtin.dnf:
        name: epel-release
        state: present

    - name: Install nginx
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: Start and enable nginx
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

    - name: Open firewall ports
      ansible.posix.firewalld:
        service: "{{ item }}"
        permanent: true
        immediate: true
        state: enabled
      loop:
        - http
        - https

See also: AWX Behind Reverse Proxy: Nginx, Traefik, Caddy & Apache Setup Guide

Configure Virtual Hosts

- name: Configure nginx virtual hosts
  hosts: webservers
  become: true
  vars:
    sites:
      - name: myapp.example.com
        root: /var/www/myapp
        port: 80
      - name: api.example.com
        root: /var/www/api
        port: 80

  tasks:
    - name: Create document roots
      ansible.builtin.file:
        path: "{{ item.root }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
      loop: "{{ sites }}"

    - name: Deploy virtual host configs
      ansible.builtin.template:
        src: vhost.conf.j2
        dest: "/etc/nginx/sites-available/{{ item.name }}.conf"
      loop: "{{ sites }}"
      notify: reload nginx

    - name: Enable virtual hosts
      ansible.builtin.file:
        src: "/etc/nginx/sites-available/{{ item.name }}.conf"
        dest: "/etc/nginx/sites-enabled/{{ item.name }}.conf"
        state: link
      loop: "{{ sites }}"
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
{# templates/vhost.conf.j2 #}
server {
    listen {{ item.port }};
    server_name {{ item.name }};
    root {{ item.root }};
    index index.html;

    access_log /var/log/nginx/{{ item.name }}-access.log;
    error_log /var/log/nginx/{{ item.name }}-error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}

SSL/TLS with Let's Encrypt

- name: Setup SSL with Certbot
  hosts: webservers
  become: true
  vars:
    domain: myapp.example.com
    email: admin@example.com

  tasks:
    - name: Install Certbot
      ansible.builtin.apt:
        name:
          - certbot
          - python3-certbot-nginx
        state: present

    - name: Obtain SSL certificate
      ansible.builtin.command: >
        certbot --nginx -d {{ domain }}
        --non-interactive --agree-tos
        --email {{ email }}
        --redirect
      args:
        creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"

    - name: Setup auto-renewal
      ansible.builtin.cron:
        name: "certbot renewal"
        special_time: weekly
        job: "certbot renew --quiet --post-hook 'systemctl reload nginx'"

See also: Automating Nginx Reverse Proxy Setup for Flask on RHEL

Reverse Proxy

# templates/reverse-proxy.conf.j2
upstream backend {
{% for server in backend_servers %}
    server {{ server }}:{{ backend_port | default(8080) }};
{% endfor %}
}

server {
    listen 443 ssl http2;
    server_name {{ domain }};

    ssl_certificate /etc/letsencrypt/live/{{ domain }}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{ domain }}/privkey.pem;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Load Balancing

upstream app_servers {
{% for server in groups['appservers'] %}
    server {{ hostvars[server].ansible_host }}:8080{% if server == groups['appservers'][0] %} weight=3{% endif %};
{% endfor %}
}

server {
    listen 80;
    server_name {{ domain }};

    location / {
        proxy_pass http://app_servers;
        proxy_next_upstream error timeout http_502 http_503;
    }
}

See also: Renew and Change SSL/TLS Certificates in AAP 2.7: Operator-Based OpenShift Installation

Security Hardening

- name: Deploy hardened nginx config
  ansible.builtin.template:
    src: nginx-security.conf.j2
    dest: /etc/nginx/conf.d/security.conf
  notify: reload nginx
{# templates/nginx-security.conf.j2 #}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;

# Hide nginx version
server_tokens off;

# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;

FAQ

Should I use a role or write my own tasks?

For production, use a maintained role like geerlingguy.nginx from Ansible Galaxy. Write custom tasks for specific requirements or learning.

How do I test nginx config changes safely?

Use validate parameter: ansible.builtin.template: validate: 'nginx -t -c %s'. This runs nginx -t before applying changes.

How do I handle nginx on both Debian and RedHat?

Use ansible_os_family conditions or role defaults: config paths differ (/etc/nginx/sites-enabled/ on Debian vs /etc/nginx/conf.d/ on RedHat).

Install Nginx

- name: Install Nginx
  ansible.builtin.apt:
    name: nginx
    state: present
    update_cache: true
  become: true

- name: Start and enable
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true
  become: true

Configure Virtual Host

- name: Deploy vhost config
  ansible.builtin.template:
    src: vhost.conf.j2
    dest: /etc/nginx/sites-available/{{ domain }}
  become: true
  notify: reload nginx

- name: Enable vhost
  ansible.builtin.file:
    src: /etc/nginx/sites-available/{{ domain }}
    dest: /etc/nginx/sites-enabled/{{ domain }}
    state: link
  become: true
  notify: reload nginx

- name: Remove default site
  ansible.builtin.file:
    path: /etc/nginx/sites-enabled/default
    state: absent
  become: true
  notify: reload nginx
{# templates/vhost.conf.j2 #}
server {
    listen 80;
    server_name {{ domain }} www.{{ domain }};
    root {{ document_root }};
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    access_log /var/log/nginx/{{ domain }}-access.log;
    error_log /var/log/nginx/{{ domain }}-error.log;
}

Reverse Proxy

server {
    listen 80;
    server_name {{ domain }};

    location / {
        proxy_pass http://{{ backend_host }}:{{ backend_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

SSL with Let's Encrypt

- name: Install certbot
  apt:
    name: [certbot, python3-certbot-nginx]
  become: true

- name: Obtain SSL certificate
  command: >
    certbot --nginx -d {{ domain }} -d www.{{ domain }}
    --non-interactive --agree-tos -m {{ admin_email }}
  become: true
  args:
    creates: /etc/letsencrypt/live/{{ domain }}

- name: Auto-renewal cron
  cron:
    name: "certbot renewal"
    minute: "0"
    hour: "3"
    job: "certbot renew --quiet"
  become: true

Load Balancer

upstream backend {
{% for host in groups['appservers'] %}
    server {{ hostvars[host].ansible_host }}:8080;
{% endfor %}
}

server {
    listen 80;
    server_name {{ domain }};

    location / {
        proxy_pass http://backend;
    }
}

Complete Role

# roles/nginx/tasks/main.yml
- apt: name=nginx state=present update_cache=true
  become: true

- template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: nginx -t -c %s
  become: true
  notify: reload nginx

- template:
    src: vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ item.domain }}"
  loop: "{{ nginx_sites }}"
  become: true
  notify: reload nginx

- file:
    src: "/etc/nginx/sites-available/{{ item.domain }}"
    dest: "/etc/nginx/sites-enabled/{{ item.domain }}"
    state: link
  loop: "{{ nginx_sites }}"
  become: true

- service: name=nginx state=started enabled=true
  become: true

FAQ

How do I validate config before reload?

- template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: nginx -t -c %s

The validate parameter runs the check before writing the file.

reload vs restart?

reload applies config changes without dropping connections. restart stops/starts the process. Always prefer reload for config changes.

How do I handle multiple sites?

Loop over a list of site configs and generate per-site files in sites-available/.

Install Nginx

- ansible.builtin.apt:
    name: nginx
    state: present
    update_cache: true
  become: true

- service:
    name: nginx
    state: started
    enabled: true
  become: true

Virtual Host Template

- template:
    src: vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ domain }}"
  become: true
  notify: reload nginx

- file:
    src: "/etc/nginx/sites-available/{{ domain }}"
    dest: "/etc/nginx/sites-enabled/{{ domain }}"
    state: link
  become: true
  notify: reload nginx
{# templates/vhost.conf.j2 #}
server {
    listen 80;
    server_name {{ domain }} www.{{ domain }};
    root {{ document_root }};
    index index.html;

    access_log /var/log/nginx/{{ domain }}_access.log;
    error_log /var/log/nginx/{{ domain }}_error.log;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

Reverse Proxy

server {
    listen 80;
    server_name {{ domain }};

    location / {
        proxy_pass http://{{ backend_host }}:{{ backend_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

SSL with Let's Encrypt

- apt: { name: [certbot, python3-certbot-nginx] }
  become: true

- command: >
    certbot --nginx -d {{ domain }} -d www.{{ domain }}
    --non-interactive --agree-tos --email {{ admin_email }}
  become: true
  args:
    creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"

Load Balancer

upstream backend {
    {% for server in backend_servers %}
    server {{ server }}:{{ backend_port }} weight={{ loop.index0 == 0 | ternary(3, 1) }};
    {% endfor %}
}

server {
    listen 80;
    server_name {{ domain }};

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
    }
}

Security Headers

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Handler

handlers:
  - name: reload nginx
    service:
      name: nginx
      state: reloaded
    become: true

  - name: restart nginx
    service:
      name: nginx
      state: restarted
    become: true

Validate Config Before Reload

- template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: "nginx -t -c %s"
  become: true
  notify: reload nginx

FAQ

How to remove the default site?

- file: { path: /etc/nginx/sites-enabled/default, state: absent }
  notify: reload nginx

How to check Nginx status?

- command: nginx -t
  become: true
  register: nginx_status

reload vs restart?

Reload applies config changes gracefully (no downtime). Restart fully stops and starts Nginx (brief downtime). Always prefer reload.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home