CloudOpsGuide
fundamentals

Nginx Reverse Proxy and TLS: A Production Setup Guide

Intermediate
10 minutes
October 2026
CloudOpsGuide Team

Nginx Reverse Proxy and TLS: A Production Setup Guide

Nginx is the Swiss Army knife sitting in front of most production stacks: TLS termination, reverse proxying, static files, caching, rate limiting. This tutorial takes a fresh Nginx install to a hardened TLS reverse proxy.

Table of Contents

Install and verify

# Debian/Ubuntu
sudo apt update && sudo apt install nginx
sudo systemctl enable --now nginx
curl -I http://localhost    # 200 OK

Config lives in /etc/nginx/nginx.conf with sites in sites-available/, enabled via symlinks into sites-enabled/.

Reverse proxy: the core pattern

Your app listens on 127.0.0.1:3000; Nginx faces the internet:

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

Those headers matter — your app sees the real client IP and original scheme, not nginx's. Without X-Forwarded-Proto, apps often break on redirects or mixed-content detection.

sudo nginx -t          # always test before reload
sudo systemctl reload nginx

Free TLS with Let's Encrypt

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com

Certbot edits the config, adds the TLS block, and sets up auto-renewal. Verify with sudo certbot renew --dry-run. This turns your port-80 proxy into a proper HTTPS endpoint in about 30 seconds.

Load balancing: upstreams

upstream app_backend {
    least_conn;                    # send to least-busy
    server 10.0.1.11:3000;
    server 10.0.1.12:3000;
    server 10.0.1.13:3000 backup;  # only if others fail
}

location / {
    proxy_pass http://app_backend;
}

Production hardening checklist

# inside http {} or server {}
client_max_body_size 10m;          # reject huge uploads
server_tokens off;                 # don't advertise version
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy strict-origin-when-cross-origin;

# rate limiting — protects login endpoints
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location /api/login {
    limit_req zone=login burst=10 nodelay;
    proxy_pass http://app_backend;
}

Reading logs like a pro

tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head

When nginx is the wrong tool

If you're on Kubernetes, use an Ingress controller (NGINX Ingress, Traefik). For pure L4 TCP at massive scale, look at HAProxy or a cloud NLB. But for VMs, containers on a single host, and everything in between — this config covers 90% of production needs.

Related Articles


Last Updated: October 2026 Author: CloudOpsGuide Team Difficulty: Intermediate Estimated Reading Time: 10 minutes