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: Ansible London Meetup 2024 Recap: Highlights and Insights

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.

Related Articles

Ansible Galaxy guiderendering files with Ansible templateAnsible handlers guideAnsible become guideAnsible Cron Module GuideAnsible firewalld & ufw Modules Guide

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home