Configure NGINX reverse proxy with SSL termination and load balancing for high availability

Intermediate 25 min Apr 12, 2026 47 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up NGINX as a reverse proxy with SSL termination, load balancing across multiple backend servers, and automatic HTTPS redirection for production-ready high availability web infrastructure.

Prerequisites

  • Root or sudo access
  • Domain name pointing to your server
  • Multiple backend application servers
  • Basic understanding of web server concepts

What this solves

NGINX reverse proxy with SSL termination allows you to centralize SSL certificate management while distributing traffic across multiple backend application servers. This setup improves performance, enables zero-downtime deployments, and provides a single point for security hardening and caching.

Step-by-step installation

Update system packages and install NGINX

Start by updating your package manager and installing NGINX with all required modules for reverse proxy functionality.

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx openssl curl
sudo dnf update -y
sudo dnf install -y nginx openssl curl

Enable and start NGINX service

Enable NGINX to start automatically on boot and verify it's running correctly.

sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx

Configure firewall rules

Open HTTP and HTTPS ports in the system firewall to allow web traffic.

sudo ufw allow 'Nginx Full'
sudo ufw status
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Create SSL certificates directory

Create a secure directory structure for SSL certificates with appropriate permissions.

sudo mkdir -p /etc/nginx/ssl
sudo chmod 700 /etc/nginx/ssl

Generate self-signed SSL certificate for testing

Create a temporary self-signed certificate for initial testing. Replace with Let's Encrypt or commercial certificates in production.

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/example.com.key \
  -out /etc/nginx/ssl/example.com.crt \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"

Set SSL certificate permissions

Secure the SSL certificates with proper ownership and restrictive permissions.

sudo chown root:root /etc/nginx/ssl/*
sudo chmod 600 /etc/nginx/ssl/example.com.key
sudo chmod 644 /etc/nginx/ssl/example.com.crt

Create upstream configuration

Define upstream server groups for load balancing across multiple backend application servers.

upstream app_servers {
    least_conn;
    server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 weight=2 max_fails=3 fail_timeout=30s backup;
}

upstream api_servers {
    ip_hash;
    server 192.168.1.20:3000 weight=1 max_fails=2 fail_timeout=20s;
    server 192.168.1.21:3000 weight=1 max_fails=2 fail_timeout=20s;
}

Health check configuration

server { listen 127.0.0.1:8081; location /nginx-health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } }

Create SSL configuration snippet

Define reusable SSL settings with modern security headers and cipher suites.

# SSL Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

SSL session cache and timeout

ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off;

OCSP stapling

ssl_stapling on; ssl_stapling_verify on;

Security headers

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Frame-Options DENY 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;

Remove server tokens

server_tokens off;

Configure main reverse proxy virtual host

Create the primary virtual host configuration with SSL termination and reverse proxy settings.

server {
    listen 80;
    server_name example.com www.example.com;
    
    # Redirect all HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    
    # SSL certificates
    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    
    # Include SSL configuration
    include /etc/nginx/conf.d/ssl.conf;
    
    # Logging
    access_log /var/log/nginx/reverse-proxy.access.log;
    error_log /var/log/nginx/reverse-proxy.error.log;
    
    # Main application proxy
    location / {
        proxy_pass http://app_servers;
        proxy_http_version 1.1;
        
        # Proxy headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_set_header X-Forwarded-Host $server_name;
        
        # Proxy timeouts
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffer settings
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;
        
        # Cache control
        proxy_cache_bypass $http_upgrade;
    }
    
    # API endpoint with session persistence
    location /api/ {
        proxy_pass http://api_servers/;
        proxy_http_version 1.1;
        
        # Same proxy headers as above
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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;
        
        # API-specific timeouts
        proxy_connect_timeout 3s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
    
    # Static assets with caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
        proxy_pass http://app_servers;
        proxy_cache_valid 200 1h;
        expires 1h;
        add_header Cache-Control "public, immutable";
    }
    
    # Health check endpoint
    location /health {
        access_log off;
        proxy_pass http://app_servers/health;
        proxy_connect_timeout 1s;
        proxy_send_timeout 1s;
        proxy_read_timeout 1s;
    }
}

Enable the virtual host

Create a symbolic link to enable the reverse proxy configuration and remove the default site.

sudo ln -s /etc/nginx/sites-available/reverse-proxy /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/reverse-proxy /etc/nginx/conf.d/reverse-proxy.conf
sudo rm -f /etc/nginx/conf.d/default.conf

Optimize NGINX configuration

Update the main NGINX configuration for better performance and security.

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    # Basic settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 16M;
    
    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                   '$status $body_bytes_sent "$http_referer" '
                   '"$http_user_agent" "$http_x_forwarded_for" '
                   'rt=$request_time uct="$upstream_connect_time" '
                   'uht="$upstream_header_time" urt="$upstream_response_time"';
    
    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml;
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=general:10m rate=1r/s;
    
    # Connection limiting
    limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
    limit_conn conn_limit_per_ip 20;
    
    # Include configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Set up Let's Encrypt SSL certificates

Install Certbot and obtain production SSL certificates from Let's Encrypt.

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Configure automatic certificate renewal

Set up automatic renewal for Let's Encrypt certificates using a systemd timer.

sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
sudo systemctl status certbot.timer

Test and reload NGINX configuration

Verify the configuration syntax and reload NGINX to apply all changes.

sudo nginx -t
sudo systemctl reload nginx

Verify your setup

Test your reverse proxy configuration with these verification commands:

# Check NGINX status and configuration
sudo systemctl status nginx
sudo nginx -T

Test HTTP to HTTPS redirect

curl -I http://example.com

Test SSL certificate

openssl s_client -connect example.com:443 -servername example.com

Test load balancer health

curl -k https://example.com/health

Check upstream server status

echo "GET /nginx-health HTTP/1.0\r\n\r\n" | nc 127.0.0.1 8081

Monitor access logs

sudo tail -f /var/log/nginx/reverse-proxy.access.log

Common issues

Symptom Cause Fix
502 Bad Gateway error Backend servers not responding Check upstream server status and network connectivity
SSL certificate errors Wrong certificate path or permissions Verify certificate files with sudo nginx -t
Rate limiting blocks legitimate users Too restrictive rate limits Adjust rate limits in nginx.conf or add IP whitelist
Slow response times Poor upstream configuration Tune proxy timeouts and buffer settings
Session persistence not working Wrong load balancing method Use ip_hash for sticky sessions
Note: Monitor your setup regularly using tools like Prometheus and Grafana for comprehensive observability.

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.