Welcome to Part 18 of our comprehensive NGINX on Ubuntu series! We’ll implement automation and DevOps practices to streamline NGINX deployment, configuration management, and continuous integration workflows.
DevOps and Automation Fundamentals
NGINX automation involves Infrastructure as Code (IaC), configuration management, CI/CD pipelines, and automated testing to ensure consistent, reliable, and scalable deployments.
graph TD A[DevOps Automation] --> B[Infrastructure as Code] A --> C[Configuration Management] A --> D[CI/CD Pipelines] A --> E[Automated Testing] B --> F[TerraformCloudFormationAnsible Playbooks] C --> G[Version ControlTemplate ManagementEnvironment Sync] D --> H[Build AutomationDeployment PipelineRollback Strategy] E --> I[Config ValidationLoad TestingSecurity Scanning] J[Automation Benefits] --> K[Consistency] J --> L[Reliability] J --> M[Scalability] J --> N[Speed] style A fill:#e1f5fe style J fill:#e8f5e8 style F fill:#fff3e0 style G fill:#e3f2fd style H fill:#e8f5e8 style I fill:#fff3e0
Ansible Playbook for NGINX
# Install Ansible and create project structure
sudo apt update
sudo apt install ansible -y
mkdir -p /opt/nginx-automation/{playbooks,roles,inventory,templates,vars}
cd /opt/nginx-automation
# Create NGINX deployment playbook
cat > playbooks/nginx-deployment.yml << 'EOF'
---
- name: Deploy and Configure NGINX
hosts: nginx_servers
become: yes
vars_files:
- ../vars/nginx-config.yml
tasks:
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install NGINX and dependencies
apt:
name:
- nginx
- certbot
- python3-certbot-nginx
- curl
state: present
- name: Start and enable NGINX
systemd:
name: nginx
state: started
enabled: yes
- name: Backup existing configuration
copy:
src: /etc/nginx/nginx.conf
dest: "/etc/nginx/nginx.conf.backup.{{ ansible_date_time.epoch }}"
remote_src: yes
backup: yes
- name: Deploy main NGINX configuration
template:
src: ../templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
notify:
- validate nginx config
- reload nginx
- name: Create virtual host configurations
template:
src: "../templates/{{ item.template }}"
dest: "/etc/nginx/sites-available/{{ item.name }}"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_sites }}"
notify:
- validate nginx config
- reload nginx
- name: Enable sites
file:
src: "/etc/nginx/sites-available/{{ item.name }}"
dest: "/etc/nginx/sites-enabled/{{ item.name }}"
state: link
loop: "{{ nginx_sites }}"
when: item.enabled | default(true)
notify:
- validate nginx config
- reload nginx
- name: Remove default site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify:
- validate nginx config
- reload nginx
- name: Configure firewall
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- '80'
- '443'
when: configure_firewall | default(true)
- name: Deploy monitoring scripts
template:
src: "../templates/{{ item }}.j2"
dest: "/usr/local/bin/{{ item }}"
owner: root
group: root
mode: '0755'
loop:
- nginx-health-check
- nginx-backup
- nginx-deploy
handlers:
- name: validate nginx config
command: nginx -t
register: nginx_syntax
failed_when: nginx_syntax.rc != 0
- name: reload nginx
systemd:
name: nginx
state: reloaded
when: nginx_syntax is succeeded
EOF
Configuration Templates
# Create main NGINX configuration template
cat > templates/nginx.conf.j2 << 'EOF'
# {{ ansible_managed }}
user {{ nginx_user | default('www-data') }};
worker_processes {{ nginx_worker_processes | default('auto') }};
worker_rlimit_nofile {{ nginx_worker_rlimit | default('65535') }};
pid /var/run/nginx.pid;
events {
use {{ nginx_events_use | default('epoll') }};
worker_connections {{ nginx_worker_connections | default('4096') }};
multi_accept {{ nginx_multi_accept | default('on') }};
}
http {
sendfile {{ nginx_sendfile | default('on') }};
tcp_nopush {{ nginx_tcp_nopush | default('on') }};
tcp_nodelay {{ nginx_tcp_nodelay | default('on') }};
keepalive_timeout {{ nginx_keepalive_timeout | default('65') }};
client_max_body_size {{ nginx_client_max_body_size | default('64m') }};
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" rt=$request_time';
access_log {{ nginx_access_log | default('/var/log/nginx/access.log') }} main;
error_log {{ nginx_error_log | default('/var/log/nginx/error.log') }} warn;
gzip {{ nginx_gzip | default('on') }};
gzip_vary on;
gzip_comp_level {{ nginx_gzip_comp_level | default('6') }};
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript;
ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.2 TLSv1.3') }};
ssl_session_cache {{ nginx_ssl_session_cache | default('shared:SSL:50m') }};
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
EOF
# Create virtual host template
cat > templates/virtual-host.conf.j2 << 'EOF'
# {{ ansible_managed }}
# Virtual Host: {{ server_name }}
{% if upstream_servers is defined %}
upstream {{ upstream_name | default('backend') }} {
{% for server in upstream_servers %}
server {{ server.address }}:{{ server.port | default('8080') }};
{% endfor %}
keepalive 32;
}
{% endif %}
server {
{% if enable_ssl | default(false) %}
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/{{ server_name }}.crt;
ssl_certificate_key /etc/ssl/private/{{ server_name }}.key;
{% else %}
listen 80;
{% endif %}
server_name {{ server_name }};
{% if document_root is defined %}
root {{ document_root }};
index index.html index.htm;
{% endif %}
access_log /var/log/nginx/{{ server_name }}-access.log main;
error_log /var/log/nginx/{{ server_name }}-error.log warn;
location / {
{% if upstream_servers is defined %}
proxy_pass http://{{ upstream_name | default('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;
{% else %}
try_files $uri $uri/ =404;
{% endif %}
}
location = /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
EOF
Configuration Variables
# Create configuration variables
cat > vars/nginx-config.yml << 'EOF'
---
# NGINX Configuration Variables
nginx_user: www-data
nginx_worker_processes: auto
nginx_worker_connections: 4096
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 64m
nginx_gzip: "on"
nginx_gzip_comp_level: 6
nginx_sites:
- name: "api.example.com"
template: "virtual-host.conf.j2"
server_name: "api.example.com"
enable_ssl: true
upstream_name: "api_backend"
upstream_servers:
- address: "192.168.1.10"
port: 8080
- address: "192.168.1.11"
port: 8080
enabled: true
- name: "www.example.com"
template: "virtual-host.conf.j2"
server_name: "www.example.com"
enable_ssl: false
document_root: "/var/www/html"
enabled: true
configure_firewall: true
EOF
CI/CD Pipeline
graph TD A[Code Commit] --> B[CI Trigger] B --> C[Config Validation] C --> D[Security Scan] D --> E[Deploy Staging] E --> F[Automated Tests] F --> G{Tests Pass?} G -->|Yes| H[Deploy Production] G -->|No| I[Rollback] H --> J[Health Checks] K[Pipeline Stages] --> L[Validate] K --> M[Test] K --> N[Deploy] K --> O[Monitor] style A fill:#e1f5fe style K fill:#e8f5e8 style G fill:#fff3e0 style H fill:#e8f5e8 style I fill:#ffebee
# Create GitHub Actions workflow
mkdir -p .github/workflows
cat > .github/workflows/nginx-deploy.yml << 'EOF'
name: NGINX Deployment Pipeline
on:
push:
branches: [ main, staging ]
paths: ['nginx/**', 'ansible/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Ansible
run: sudo apt install -y ansible
- name: Validate playbook syntax
run: |
cd ansible
ansible-playbook --syntax-check playbooks/nginx-deployment.yml
- name: Lint configuration
run: |
# Install nginx for syntax checking
sudo apt install -y nginx
# Render templates and validate
cd ansible
ansible-playbook --check --diff playbooks/nginx-deployment.yml
deploy-staging:
runs-on: ubuntu-latest
needs: validate
if: github.ref == 'refs/heads/staging'
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
cd ansible
ansible-playbook -i inventory/staging \
playbooks/nginx-deployment.yml
- name: Health check
run: |
sleep 30
curl -f http://staging.example.com/health
deploy-production:
runs-on: ubuntu-latest
needs: validate
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
cd ansible
ansible-playbook -i inventory/production \
playbooks/nginx-deployment.yml
- name: Health check
run: |
sleep 60
curl -f https://api.example.com/health
curl -f https://www.example.com/health
EOF
Infrastructure as Code
# Create Terraform configuration
mkdir -p terraform/nginx-infrastructure
cat > terraform/nginx-infrastructure/main.tf << 'EOF'
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# VPC
resource "aws_vpc" "nginx_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "nginx-vpc"
}
}
# Subnet
resource "aws_subnet" "nginx_subnet" {
vpc_id = aws_vpc.nginx_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "nginx-subnet"
}
}
# Internet Gateway
resource "aws_internet_gateway" "nginx_igw" {
vpc_id = aws_vpc.nginx_vpc.id
tags = {
Name = "nginx-igw"
}
}
# Route Table
resource "aws_route_table" "nginx_rt" {
vpc_id = aws_vpc.nginx_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.nginx_igw.id
}
tags = {
Name = "nginx-rt"
}
}
resource "aws_route_table_association" "nginx_rta" {
subnet_id = aws_subnet.nginx_subnet.id
route_table_id = aws_route_table.nginx_rt.id
}
# Security Group
resource "aws_security_group" "nginx_sg" {
name_prefix = "nginx-sg"
vpc_id = aws_vpc.nginx_vpc.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# EC2 Instances
resource "aws_instance" "nginx_servers" {
count = var.nginx_instance_count
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
key_name = var.key_pair_name
vpc_security_group_ids = [aws_security_group.nginx_sg.id]
subnet_id = aws_subnet.nginx_subnet.id
user_data = <<-EOF
#!/bin/bash
apt update
apt install -y python3 python3-pip
pip3 install ansible
EOF
tags = {
Name = "nginx-server-${count.index + 1}"
Role = "nginx"
}
}
# Data sources
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hv-ssd/ubuntu-22.04-amd64-server-*"]
}
}
EOF
# Create Terraform variables
cat > terraform/nginx-infrastructure/variables.tf << 'EOF'
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "nginx_instance_count" {
description = "Number of NGINX instances"
type = number
default = 2
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.medium"
}
variable "key_pair_name" {
description = "AWS key pair name"
type = string
}
EOF
Deployment Scripts
# Create deployment automation script
cat > /usr/local/bin/nginx-deploy-automation.sh << 'EOF'
#!/bin/bash
# NGINX Deployment Automation Script
PLAYBOOK_DIR="/opt/nginx-automation"
LOG_FILE="/var/log/nginx-deploy.log"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
deploy_nginx() {
local environment="${1:-staging}"
local inventory_file="$PLAYBOOK_DIR/inventory/$environment"
log_message "Starting NGINX deployment to $environment"
# Validate inventory
if [ ! -f "$inventory_file" ]; then
log_message "ERROR: Inventory file not found: $inventory_file"
return 1
fi
# Run pre-deployment checks
log_message "Running pre-deployment validation..."
ansible-playbook --check \
-i "$inventory_file" \
"$PLAYBOOK_DIR/playbooks/nginx-deployment.yml"
if [ $? -ne 0 ]; then
log_message "ERROR: Pre-deployment validation failed"
return 1
fi
# Execute deployment
log_message "Executing deployment..."
ansible-playbook \
-i "$inventory_file" \
"$PLAYBOOK_DIR/playbooks/nginx-deployment.yml" \
--extra-vars "environment=$environment"
if [ $? -eq 0 ]; then
log_message "Deployment successful"
# Run post-deployment tests
run_health_checks "$environment"
else
log_message "ERROR: Deployment failed"
return 1
fi
}
run_health_checks() {
local environment="$1"
log_message "Running health checks for $environment"
case "$environment" in
staging)
curl -f -s --max-time 10 http://staging.example.com/health
;;
production)
curl -f -s --max-time 10 https://api.example.com/health
curl -f -s --max-time 10 https://www.example.com/health
;;
esac
if [ $? -eq 0 ]; then
log_message "Health checks passed"
else
log_message "WARNING: Health checks failed"
fi
}
rollback_deployment() {
local environment="${1:-staging}"
log_message "Initiating rollback for $environment"
# Restore previous configuration backup
ansible-playbook \
-i "$PLAYBOOK_DIR/inventory/$environment" \
"$PLAYBOOK_DIR/playbooks/nginx-rollback.yml"
log_message "Rollback completed"
}
case "${1:-deploy}" in
deploy)
deploy_nginx "$2"
;;
rollback)
rollback_deployment "$2"
;;
health-check)
run_health_checks "$2"
;;
*)
echo "Usage: $0 {deploy|rollback|health-check} [environment]"
echo "Environments: staging, production"
;;
esac
# Make executable: sudo chmod +x /usr/local/bin/nginx-deploy-automation.sh
EOF
Testing and Usage
# Setup and test automation
# 1. Initialize Ansible project
cd /opt/nginx-automation
ansible-galaxy init roles/nginx
# 2. Create inventory files
mkdir -p inventory
cat > inventory/staging << 'EOF'
[nginx_servers]
staging-nginx-1 ansible_host=192.168.1.10
staging-nginx-2 ansible_host=192.168.1.11
EOF
cat > inventory/production << 'EOF'
[nginx_servers]
prod-nginx-1 ansible_host=10.0.1.10
prod-nginx-2 ansible_host=10.0.1.11
EOF
# 3. Test playbook syntax
ansible-playbook --syntax-check playbooks/nginx-deployment.yml
# 4. Run dry-run deployment
ansible-playbook --check -i inventory/staging playbooks/nginx-deployment.yml
# 5. Deploy to staging
/usr/local/bin/nginx-deploy-automation.sh deploy staging
# 6. Run health checks
/usr/local/bin/nginx-deploy-automation.sh health-check staging
# 7. Deploy infrastructure with Terraform
cd terraform/nginx-infrastructure
terraform init
terraform plan
terraform apply
# 8. Trigger CI/CD pipeline
git add .
git commit -m "Update NGINX configuration"
git push origin staging # Triggers staging deployment
What’s Next?
Excellent! You’ve implemented comprehensive DevOps automation for NGINX with Infrastructure as Code, configuration management, and CI/CD pipelines. Your deployment process is now automated, consistent, and scalable.
Coming up in Part 19: NGINX Container Deployment with Docker and Kubernetes
References
This is Part 18 of our 22-part NGINX series. Your infrastructure is now fully automated! Next, we’ll containerize NGINX with Docker and Kubernetes. Questions? Share them in the comments!