Introduction
Nginx is the most popular web server and reverse proxy. Ansible automates the full lifecycle — install, configure virtual hosts, deploy SSL certificates, set up reverse proxy, and manage load balancing across your fleet.
Install Nginx
# Ubuntu/Debian
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
# RHEL/CentOS
- name: Install Nginx
ansible.builtin.yum:
name: nginx
state: present
# Start and enable
- name: Start Nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
Basic Configuration
# Deploy main config
- name: Configure Nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: "nginx -t -c %s"
notify: Reload Nginx
handlers:
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloaded
nginx.conf.j2
# templates/nginx.conf.j2
user www-data;
worker_processes {{ ansible_processor_vcpus }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml;
# Virtual hosts
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Virtual Host (Server Block)
- name: Deploy virtual host
ansible.builtin.template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ domain }}.conf"
validate: "nginx -t -c /etc/nginx/nginx.conf"
notify: Reload Nginx
- name: Enable virtual host
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ domain }}.conf"
dest: "/etc/nginx/sites-enabled/{{ domain }}.conf"
state: link
notify: Reload Nginx
- name: Remove default site
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload Nginx
vhost.conf.j2
# templates/vhost.conf.j2
server {
listen 80;
server_name {{ domain }} www.{{ domain }};
root {{ document_root | default('/var/www/' + domain) }};
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# 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;
# Logging
access_log /var/log/nginx/{{ domain }}.access.log;
error_log /var/log/nginx/{{ domain }}.error.log;
}
SSL/TLS with Let's Encrypt
- name: Install Certbot
ansible.builtin.apt:
name: [certbot, python3-certbot-nginx]
state: present
- name: Obtain SSL certificate
ansible.builtin.command: >
certbot --nginx -d {{ domain }} -d www.{{ domain }}
--non-interactive --agree-tos -m {{ admin_email }}
args:
creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"
- name: Auto-renew cron
ansible.builtin.cron:
name: "Certbot renewal"
job: "certbot renew --quiet --post-hook 'systemctl reload nginx'"
hour: "3"
minute: "30"
weekday: "1"
SSL Virtual Host Template
# templates/vhost-ssl.conf.j2
server {
listen 80;
server_name {{ domain }} www.{{ domain }};
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name {{ domain }} www.{{ domain }};
root {{ document_root }};
ssl_certificate /etc/letsencrypt/live/{{ domain }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ domain }}/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
try_files $uri $uri/ =404;
}
}
Reverse Proxy
# templates/reverse-proxy.conf.j2
upstream {{ app_name }} {
{% for server in app_servers %}
server {{ server }}:{{ app_port | default(8080) }};
{% endfor %}
}
server {
listen 80;
server_name {{ domain }};
location / {
proxy_pass http://{{ app_name }};
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;
proxy_read_timeout 90;
}
}
- name: Deploy reverse proxy
ansible.builtin.template:
src: reverse-proxy.conf.j2
dest: /etc/nginx/sites-available/myapp.conf
vars:
app_name: myapp
domain: app.example.com
app_servers: "{{ groups['appservers'] | map('extract', hostvars, 'ansible_host') | list }}"
app_port: 8080
notify: Reload Nginx
Load Balancing
# templates/load-balancer.conf.j2
upstream backend {
# Round-robin (default)
{% for server in backend_servers %}
server {{ server.host }}:{{ server.port }} weight={{ server.weight | default(1) }};
{% endfor %}
# Health check
keepalive 32;
}
# Or: least_conn, ip_hash
upstream backend_sticky {
ip_hash;
{% for server in backend_servers %}
server {{ server.host }}:{{ server.port }};
{% endfor %}
}
Complete Web Server Playbook
---
- name: Setup Nginx web server
hosts: webservers
become: true
vars:
domain: example.com
admin_email: admin@example.com
document_root: /var/www/{{ domain }}
tasks:
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Create document root
ansible.builtin.file:
path: "{{ document_root }}"
state: directory
owner: www-data
group: www-data
- name: Deploy Nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: "nginx -t -c %s"
notify: Reload Nginx
- name: Deploy virtual host
ansible.builtin.template:
src: vhost.conf.j2
dest: /etc/nginx/sites-available/{{ domain }}.conf
notify: Reload Nginx
- name: Enable site
ansible.builtin.file:
src: /etc/nginx/sites-available/{{ domain }}.conf
dest: /etc/nginx/sites-enabled/{{ domain }}.conf
state: link
notify: Reload Nginx
- name: Start Nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloaded
Troubleshooting
"nginx: [emerg] bind() to 0.0.0.0:80 failed"
Port 80 already in use:
- name: Check what's using port 80
ansible.builtin.command: ss -tlnp | grep :80
changed_when: false
Config Validation
Always validate before applying:
- template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: "nginx -t -c %s" # Fails if config is invalid
Related Articles
Conclusion
Ansible + Nginx is a powerful combination for web server automation. Use Jinja2 templates for dynamic configs that scale across hosts. Always use validate to catch config errors before they cause downtime. Use handlers for reload (not restart) to avoid dropping connections. Automate SSL with Certbot. For reverse proxy and load balancing, build upstream blocks dynamically from inventory groups.