Setup nginx reverse proxy with SSL certificates and security hardening

Intermediate 35 min Apr 03, 2026 339 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure nginx as a secure reverse proxy with Let's Encrypt SSL certificates, security headers, and DDoS protection. Learn to proxy backend applications, implement rate limiting, and harden your web server configuration.

Prerequisites

  • Root or sudo access
  • Domain name pointing to server
  • Backend application running on port 3000

What this solves

This tutorial helps you set up nginx as a secure reverse proxy that forwards client requests to backend applications while providing SSL termination, security headers, and protection against common attacks. You'll learn to configure SSL certificates with automatic renewal, implement rate limiting, and apply security hardening measures for production environments.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest security updates and package information.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install nginx and required packages

Install nginx web server along with certbot for Let's Encrypt SSL certificate management and curl for testing.

sudo apt install -y nginx certbot python3-certbot-nginx curl ufw
sudo dnf install -y nginx certbot python3-certbot-nginx curl firewalld
sudo dnf install -y epel-release

Configure firewall rules

Open HTTP and HTTPS ports in your firewall to allow web traffic to reach nginx.

sudo ufw enable
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload

Start and enable nginx

Enable nginx to start automatically on boot and start the service immediately.

sudo systemctl enable --now nginx
sudo systemctl status nginx

Create nginx main configuration

Configure the main nginx settings with security-focused parameters and optimized performance settings.

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

Load dynamic modules

include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; use epoll; multi_accept on; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 4096; server_tokens off; include /etc/nginx/mime.types; default_type application/octet-stream; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; # Rate limiting limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s; limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m; # SSL Configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_stapling on; ssl_stapling_verify on; # Gzip Settings gzip on; gzip_vary on; gzip_min_length 1024; gzip_types application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component; # Load modular configuration files include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }

Create reverse proxy configuration

Create a server block configuration for your domain with reverse proxy settings that forward requests to a backend application.

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL Configuration (will be added by certbot)
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Rate limiting
    limit_req zone=general burst=20 nodelay;
    limit_conn conn_limit_per_ip 10;

    # Logging
    access_log /var/log/nginx/example.com.access.log main;
    error_log /var/log/nginx/example.com.error.log;

    # Root location - proxy to backend
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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_cache_bypass $http_upgrade;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;
    }

    # API endpoints with stricter rate limiting
    location /api/login {
        limit_req zone=login burst=5 nodelay;
        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;
    }

    # Static assets with caching
    location /static/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        add_header Cache-Control "public, max-age=3600";
    }

    # Health check endpoint
    location /health {
        proxy_pass http://127.0.0.1:3000/health;
        access_log off;
    }

    # Block common attack patterns
    location ~* \.(sql|bak|backup|dump)$ {
        deny all;
        return 404;
    }

    # Block access to hidden files
    location ~ /\. {
        deny all;
        return 404;
    }
}

Enable the site configuration

Create the sites-enabled directory if it doesn't exist and enable your site configuration.

sudo mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
sudo ln -sf /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t

Obtain SSL certificate with Let's Encrypt

Use certbot to automatically obtain and configure SSL certificates for your domain. Replace example.com with your actual domain.

sudo certbot --nginx -d example.com -d www.example.com --email admin@example.com --agree-tos --no-eff-email
sudo systemctl reload nginx

Configure automatic certificate renewal

Set up a cron job to automatically renew SSL certificates before they expire.

sudo crontab -e

Add this line to run certificate renewal twice daily:

0 /12    /usr/bin/certbot renew --quiet && /usr/bin/systemctl reload nginx

Create upstream configuration for load balancing

If you have multiple backend servers, create an upstream block for load balancing and high availability.

upstream backend_servers {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s backup;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://backend_servers;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
    }
}

Configure log rotation

Set up log rotation to prevent nginx logs from consuming too much disk space.

/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 644 nginx adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 cat /var/run/nginx.pid
        fi
    endscript
}

Set proper file permissions

Ensure nginx configuration files have secure permissions. Never use chmod 777 as it gives full access to all users.

Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions.
sudo chown -R root:root /etc/nginx/
sudo chmod 644 /etc/nginx/nginx.conf
sudo chmod 644 /etc/nginx/sites-available/*
sudo chmod 644 /etc/nginx/conf.d/*
sudo chmod 755 /etc/nginx/sites-available/
sudo chmod 755 /etc/nginx/sites-enabled/

Test and reload nginx configuration

Test the nginx configuration for syntax errors and reload the service to apply all changes.

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx

Verify your setup

Test your nginx reverse proxy configuration with these verification commands:

# Check nginx status
sudo systemctl status nginx

Test SSL certificate

curl -I https://example.com

Check SSL certificate expiration

sudo certbot certificates

Test rate limiting (should get 429 after multiple requests)

for i in {1..15}; do curl -I https://example.com; done

Verify security headers

curl -I https://example.com | grep -E "(X-Frame-Options|X-XSS-Protection|Strict-Transport-Security)"

Check nginx error logs

sudo tail -f /var/log/nginx/error.log

Test backend connectivity

curl -H "Host: example.com" http://127.0.0.1:3000

Common issues

SymptomCauseFix
502 Bad GatewayBackend server not runningStart your backend application on port 3000
Certificate not foundLet's Encrypt failedCheck domain DNS, run sudo certbot --nginx -v
Permission denied on logsWrong log file ownershipsudo chown nginx:adm /var/log/nginx/*.log
Rate limiting not workingZone not properly configuredCheck nginx error log, verify zone syntax in nginx.conf
SSL handshake errorsWeak cipher configurationUpdate ssl_ciphers to include modern ciphers
nginx won't startConfiguration syntax errorRun sudo nginx -t to check configuration
Headers not appearingadd_header in wrong contextMove add_header to server or location block

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.