Chuyển đến nội dung chính

Lesson 7: SSL/TLS and HTTPS in Nginx

A lesson on SSL/TLS and HTTPS in Nginx — configuring SSL certificates with Let's Encrypt, HTTP to HTTPS redirect, SSL protocols and cipher optimization, HSTS, OCSP Stapling, and HTTP/2. Guide to securing connections, optimizing performance, and achieving an A+ rating on SSL Labs.

🔒 DevSecOps — Lesson 7 Lesson 7: SSL/TLS and HTTPS in Nginx

Nginx from Basics to Advanced

Part 3: Security & Performance

xdev.asia

1. Configuring SSL Certificate (Let's Encrypt)

1.1. Introduction to SSL/TLS

SSL (Secure Sockets Layer) / TLS (Transport Layer Security) are protocols for encrypting communication between client and server.

Why HTTPS is needed:

  • Data security (encryption)
  • Server authentication
  • Data integrity
  • SEO benefits (Google ranking)
  • Browser trust (no warning)
  • Required for HTTP/2
  • Required for PWA (Progressive Web Apps)

Certificate Authority (CA):

  • Let's Encrypt - Free, automated
  • DigiCert, Comodo, GlobalSign - Commercial
  • Self-signed - Development only

1.2. Install Certbot (Let's Encrypt Client)

Ubuntu/Debian:

# Update package list
sudo apt update

Install Certbot

sudo apt install certbot python3-certbot-nginx -y

Verify installation

certbot --version

CentOS/RHEL:

# Install EPEL repository
sudo yum install epel-release -y

Install Certbot

sudo yum install certbot python3-certbot-nginx -y

Or for CentOS 8+

sudo dnf install certbot python3-certbot-nginx -y

macOS:

# Using Homebrew
brew install certbot

Nginx plugin

brew install certbot-nginx

1.3. Obtain SSL Certificate — Automatic Method

Method 1: Certbot automatic configuration

# Certbot will automatically configure Nginx
sudo certbot --nginx -d example.com -d www.example.com

Follow prompts:

- Enter email address

- Agree to terms

- Choose: redirect HTTP to HTTPS (recommended)

Certbot will:

  1. Verify domain ownership
  2. Obtain certificate
  3. Automatically configure Nginx
  4. Setup auto-renewal

Check certificate:

# List certificates
sudo certbot certificates

Output:

Certificate Name: example.com

Domains: example.com www.example.com

Expiry Date: 2024-03-01 10:30:00+00:00 (VALID: 89 days)

Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem

Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem

1.4. Obtain SSL Certificate — Manual Method

Method 2: Certbot certonly (manual configuration)

# Obtain certificate without auto-config
sudo certbot certonly --nginx -d example.com -d www.example.com

Or using webroot

sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

Or using standalone (stops Nginx temporarily)

sudo systemctl stop nginx sudo certbot certonly --standalone -d example.com -d www.example.com sudo systemctl start nginx

Manual Nginx configuration:

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

# ACME challenge location
location /.well-known/acme-challenge/ {
    root /var/www/html;
}

# Redirect to HTTPS
location / {
    return 301 https://$server_name$request_uri;
}

}

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

# SSL certificate files
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# SSL configuration (more to be added)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

root /var/www/html;
index index.html;

location / {
    try_files $uri $uri/ =404;
}

}

1.5. Certificate Renewal

Let's Encrypt certificates expire after 90 days. Certbot sets up automatic renewal.

Test renewal:

# Dry run (test without actually renewing)
sudo certbot renew --dry-run

Manual renewal:

# Renew all certificates
sudo certbot renew

Renew specific certificate

sudo certbot renew --cert-name example.com

Renew and reload Nginx

sudo certbot renew --deploy-hook "systemctl reload nginx"

Automatic renewal (systemd timer):

# Check if timer is active
sudo systemctl status certbot.timer

Enable timer

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

List timers

sudo systemctl list-timers | grep certbot

Renewal hook script:

# Create renewal hook
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

#!/bin/bash

Reload Nginx after certificate renewal

systemctl reload nginx

Make executable

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

1.6. Wildcard Certificates

sudo certbot certonly --manual --preferred-challenges dns 
-d example.com -d *.example.com

Follow instructions to add DNS TXT record

_acme-challenge.example.com TXT "generated-token"

Verify DNS propagation

dig _acme-challenge.example.com TXT

Continue with certbot

1.7. Multiple Domains

# Multiple domains on one certificate
sudo certbot --nginx 
-d example.com -d www.example.com
-d blog.example.com -d shop.example.com

Or separate certificates

sudo certbot --nginx -d example.com -d www.example.com sudo certbot --nginx -d blog.example.com sudo certbot --nginx -d shop.example.com


2. HTTP to HTTPS Redirect

2.1. Simple Redirect

server {
listen 80;
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; listen [::]:443 ssl http2; server_name example.com www.example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Your site configuration
root /var/www/html;
index index.html;

}

2.2. Redirect with ACME Challenge

server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;

# Allow ACME challenge
location /.well-known/acme-challenge/ {
    root /var/www/html;
    allow all;
}

# Redirect everything else to HTTPS
location / {
    return 301 https://$server_name$request_uri;
}

}

2.3. Redirect www to non-www (HTTPS)

# Redirect www to non-www
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

return 301 https://example.com$request_uri;

}

Main site

server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Site config...

}

2.4. Complete Redirect Configuration

# HTTP - redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;

location /.well-known/acme-challenge/ {
    root /var/www/html;
}

location / {
    return 301 https://example.com$request_uri;
}

}

HTTPS www - redirect to non-www

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

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

return 301 https://example.com$request_uri;

}

Main HTTPS site

server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com;

# SSL certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

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

# Site content
root /var/www/html;
index index.html;

location / {
    try_files $uri $uri/ =404;
}

}


3. SSL Protocols and Ciphers

3.1. SSL/TLS Protocols

Available protocols:

  • SSLv2 - Deprecated, insecure ❌
  • SSLv3 - Deprecated, insecure ❌
  • TLSv1.0 - Deprecated, should avoid ⚠️
  • TLSv1.1 - Deprecated, should avoid ⚠️
  • TLSv1.2 - Secure, widely supported ✅
  • TLSv1.3 - Most secure, modern ✅

Recommended configuration:

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

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Use only TLS 1.2 and 1.3
ssl_protocols TLSv1.2 TLSv1.3;

# Prefer server ciphers (for TLS 1.2)
ssl_prefer_server_ciphers off;  # TLS 1.3 handles this automatically

}

Backward compatibility (if needed):

# Support older clients (not recommended for production)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

Modern configuration (TLS 1.3 only):

# Most secure, but may break older clients
ssl_protocols TLSv1.3;

3.2. SSL Ciphers

Ciphers determine the encryption algorithms used.

Mozilla Modern Configuration (Recommended):

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

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Protocols
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;

# Ciphers (TLS 1.3 handles automatically)

}

Mozilla Intermediate Configuration (Balanced):

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

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Protocols
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

# Ciphers for TLS 1.2
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

}

Complete SSL Configuration:

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;

# Certificate files
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Protocols
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

# Ciphers
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# Session cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

# DH parameters
ssl_dhparam /etc/nginx/dhparam.pem;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

}

3.3. Generate DH Parameters

Diffie-Hellman parameters strengthen security.

# Generate 2048-bit DH parameters (takes a few minutes)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

Or 4096-bit (takes longer, more secure)

sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

Set permissions

sudo chmod 644 /etc/nginx/dhparam.pem

Add to Nginx config:

server {
listen 443 ssl http2;

ssl_dhparam /etc/nginx/dhparam.pem;

# Other SSL config...

}

3.4. SSL Session Configuration

http {
# SSL session cache (shared across workers)
ssl_session_cache shared:SSL:10m;  # 10MB = ~40,000 sessions

# Session timeout
ssl_session_timeout 10m;           # 10 minutes

# Disable session tickets (for perfect forward secrecy)
ssl_session_tickets off;

server {
    listen 443 ssl http2;
    # Inherit from http context
}

}

Session cache sizes:

1MB = ~4,000 sessions
10MB = ~40,000 sessions
100MB = ~400,000 sessions

4. HSTS (HTTP Strict Transport Security)

HSTS instructs browsers to always use HTTPS.

4.1. Basic HSTS

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

# HSTS header
add_header Strict-Transport-Security "max-age=31536000" always;

# Other config...

}

max-age values:

# Testing - 1 hour
add_header Strict-Transport-Security "max-age=3600" always;

Short term - 1 week

add_header Strict-Transport-Security "max-age=604800" always;

Recommended - 1 year

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

Maximum - 2 years

add_header Strict-Transport-Security "max-age=63072000" always;

4.2. HSTS with includeSubDomains

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

# Apply HSTS to all subdomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

}

WARNING: includeSubDomains affects ALL subdomains. Make sure all subdomains support HTTPS.

4.3. HSTS Preload

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

# HSTS with preload directive
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

}

Submit to HSTS Preload List:

  1. Visit https://hstspreload.org/
  2. Enter your domain
  3. Check requirements:
    • Serve valid certificate
    • Redirect HTTP to HTTPS
    • Serve HSTS header on base domain
    • max-age >= 31536000 (1 year)
    • includeSubDomains directive
    • preload directive

4.4. Complete HSTS Configuration

# HTTP server - redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;

location /.well-known/acme-challenge/ {
    root /var/www/html;
}

location / {
    return 301 https://example.com$request_uri;
}

}

HTTPS server

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

# SSL config
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;

# HSTS header
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# 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;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

# Content
root /var/www/html;
index index.html;

}


5. OCSP Stapling

OCSP Stapling improves SSL/TLS handshake performance and privacy.

5.1. What is OCSP Stapling?

Without OCSP Stapling:

Client → Server: SSL handshake
Client → CA: Is certificate valid?
CA → Client: Yes, valid
Client → Server: Continue

With OCSP Stapling:

Server → CA: Is my certificate valid? (cached)
Client → Server: SSL handshake
Server → Client: Here's my certificate + OCSP response
Client: Certificate valid! (no extra request to CA)

5.2. Enable OCSP Stapling

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

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# Enable OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# Trusted certificate for verification
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

# DNS resolvers for OCSP
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

}

5.3. Verify OCSP Stapling

# Test OCSP stapling
echo QUIT | openssl s_client -connect example.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:'

Expected output:

OCSP response:

======================================

OCSP Response Status: successful (0x0)

Response Type: Basic OCSP Response

...

Cert Status: good

Online test:

# Using SSL Labs

Visit: https://www.ssllabs.com/ssltest/analyze.html?d=example.com

5.4. Complete OCSP Configuration

http {
# Global resolver (can be overridden per server)
resolver 8.8.8.8 8.8.4.4 1.1.1.1 valid=300s;
resolver_timeout 5s;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    
    # SSL protocols
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    
    # Session settings
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    
    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
    # Other 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;
    
    root /var/www/html;
    index index.html;
}

}


6. HTTP/2 Configuration

HTTP/2 significantly improves performance through multiplexing, server push, and header compression.

6.1. Enable HTTP/2

server {
# Enable HTTP/2 with http2 parameter
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# HTTP/2 requires TLS 1.2+
ssl_protocols TLSv1.2 TLSv1.3;

root /var/www/html;

}

Check if HTTP/2 is enabled:

# Test with curl
curl -I --http2 https://example.com

Look for:

HTTP/2 200

Or check browser DevTools

Network tab → Protocol column should show "h2"

6.2. HTTP/2 Push

Server Push allows the server to send resources before the client requests them.

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

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

root /var/www/html;

location / {
    # Push CSS and JS when HTML is requested
    http2_push /css/style.css;
    http2_push /js/app.js;
    
    try_files $uri $uri/ =404;
}

location = /index.html {
    # Push specific resources for homepage
    http2_push /css/style.css;
    http2_push /css/bootstrap.css;
    http2_push /js/app.js;
    http2_push /js/jquery.js;
    http2_push /images/logo.png;
}

}

Conditional push:

map $http_cookie $css_push {
default "/css/style.css";
~*visited "";  # Don't push if user visited before
}

server { listen 443 ssl http2;

location / {
    http2_push $css_push;
}

}

WARNING: HTTP/2 Push can reduce performance if used incorrectly. Only push critical resources.

6.3. HTTP/2 Parameters

http {
# HTTP/2 settings
http2_max_field_size 16k;        # Max header field size
http2_max_header_size 32k;       # Max header size
http2_max_requests 1000;          # Max requests per connection
http2_recv_timeout 30s;           # Timeout for client

server {
    listen 443 ssl http2;
    server_name example.com;
    
    # Server inherits http2 settings
}

}

6.4. Complete HTTP/2 Configuration

http {
# HTTP/2 parameters
http2_max_field_size 16k;
http2_max_header_size 32k;
http2_max_requests 1000;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    
    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    
    # Session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    root /var/www/html;
    index index.html;
    
    location / {
        # HTTP/2 Server Push for critical resources
        http2_push /css/style.css;
        http2_push /js/app.js;
        
        try_files $uri $uri/ =404;
    }
    
    # Cache static assets
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

}


7. Complete Production-Ready SSL Configuration

7.1. Optimal SSL/TLS Setup

# /etc/nginx/nginx.conf

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

events { worker_connections 1024; }

http { 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"';

access_log /var/log/nginx/access.log main;

# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 20M;

# Hide Nginx version
server_tokens off;

# SSL session cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

# OCSP settings
resolver 8.8.8.8 8.8.4.4 1.1.1.1 valid=300s;
resolver_timeout 5s;

# Gzip compression
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript 
           application/json application/javascript application/xml+rss 
           application/rss+xml font/truetype font/opentype 
           application/vnd.ms-fontobject image/svg+xml;

# HTTP/2 settings
http2_max_field_size 16k;
http2_max_header_size 32k;

# Include server configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

}

7.2. Site Configuration

# /etc/nginx/sites-available/example.com

HTTP - redirect to HTTPS

server { listen 80; listen [::]:80; server_name example.com www.example.com;

# ACME challenge
location /.well-known/acme-challenge/ {
    root /var/www/html;
    allow all;
}

# Redirect to HTTPS
location / {
    return 301 https://example.com$request_uri;
}

}

HTTPS www - redirect to non-www

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

# SSL certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

# Redirect to non-www
return 301 https://example.com$request_uri;

}

Main HTTPS server

server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com;

# Document root
root /var/www/example.com/public;
index index.html index.htm;

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

# SSL certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

# SSL protocols and ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# DH parameters
ssl_dhparam /etc/nginx/dhparam.pem;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:;" always;

# Main location
location / {
    try_files $uri $uri/ =404;
    
    # HTTP/2 Push
    http2_push /css/style.css;
    http2_push /js/app.js;
}

# Static assets
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location ~* \.(css|js)$ {
    expires 1M;
    add_header Cache-Control "public";
    access_log off;
}

location ~* \.(woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public";
    add_header Access-Control-Allow-Origin "*";
    access_log off;
}

# Deny access to hidden files
location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

# Error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

}

7.3. Enable Site

# Create symlink
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Test configuration

sudo nginx -t

Reload Nginx

sudo systemctl reload nginx


8. Testing and Optimization

8.1. SSL Labs Test

# Visit SSL Labs

https://www.ssllabs.com/ssltest/analyze.html?d=example.com

Target: A+ rating

Checklist for A+ rating:

  • ✅ TLS 1.2 and 1.3 enabled
  • ✅ Strong ciphers
  • ✅ Certificate valid and trusted
  • ✅ HSTS enabled (with preload)
  • ✅ OCSP Stapling working
  • ✅ No SSL/TLS vulnerabilities

8.2. Test Commands

# Test SSL connection
openssl s_client -connect example.com:443 -tls1_2

Test TLS 1.3

openssl s_client -connect example.com:443 -tls1_3

Test certificate

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

Test OCSP stapling

echo QUIT | openssl s_client -connect example.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:'

Test HTTP/2

curl -I --http2 https://example.com

Test with specific cipher

openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES128-GCM-SHA256

8.3. Performance Testing

# Test SSL handshake time
time openssl s_client -connect example.com:443 </dev/null

Benchmark with ab

ab -n 1000 -c 10 https://example.com/

Test with h2load (HTTP/2)

h2load -n 1000 -c 10 https://example.com/

8.4. Security Headers Check

# Check all security headers
curl -I https://example.com

Should include:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

X-Frame-Options: SAMEORIGIN

X-Content-Type-Options: nosniff

X-XSS-Protection: 1; mode=block

Referrer-Policy: no-referrer-when-downgrade

Online tools:


9. Troubleshooting

9.1. Certificate Errors

Problem: Certificate not trusted

# Check certificate chain
openssl s_client -connect example.com:443 -showcerts

Verify certificate files

sudo ls -la /etc/letsencrypt/live/example.com/

Should have:

cert.pem (certificate)

chain.pem (intermediate certificates)

fullchain.pem (cert + chain)

privkey.pem (private key)

Fix:

# Use fullchain.pem, not cert.pem
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

9.2. Mixed Content Warnings

Problem: Site loads but shows "Not Secure"

Cause: Page served over HTTPS but loads HTTP resources

Check:

# View source and look for http:// (not https://)
curl https://example.com | grep 'http://'

Fix:

<!-- Bad -->
<script src="http://example.com/js/app.js"&gt;&lt;/script>
<img src="http://example.com/image.jpg">

<!-- Good - protocol-relative --> <script src="//example.com/js/app.js"></script> <img src="//example.com/image.jpg">

<!-- Better - HTTPS --> <script src="https://example.com/js/app.js"&gt;&lt;/script> <img src="https://example.com/image.jpg">

9.3. OCSP Stapling Not Working

Problem: OCSP response not included

# Test OCSP
echo QUIT | openssl s_client -connect example.com:443 -status 2> /dev/null | grep 'OCSP response:'

If no output, check:

Fix:

server {
# Ensure these are set
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

# Add resolvers
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

}

# Test manually
sudo nginx -t
sudo systemctl reload nginx

Wait a few seconds then test again

9.4. HTTP/2 Not Working

Problem: Connection uses HTTP/1.1 instead of HTTP/2

Check:

# Test HTTP/2
curl -I --http2 https://example.com

Should show: HTTP/2 200

If shows: HTTP/1.1 200

Fix:

# Ensure http2 parameter present
listen 443 ssl http2;  # Not just: listen 443 ssl;

Restart Nginx

sudo systemctl restart nginx

9.5. Certificate Renewal Fails

Problem: Certbot renewal fails

# Check renewal
sudo certbot renew --dry-run

Fix 1: Port 80 not accessible

# Ensure port 80 open
sudo ufw allow 80
sudo firewall-cmd --permanent --add-service=http

Fix 2: Webroot not accessible

server {
listen 80;

# Ensure this location exists
location /.well-known/acme-challenge/ {
    root /var/www/html;  # Verify path is correct
    allow all;
}

}

Fix 3: Manual renewal

# Stop Nginx
sudo systemctl stop nginx

Use standalone

sudo certbot certonly --standalone -d example.com

Start Nginx

sudo systemctl start nginx


10. Practice Exercises

Exercise 1: Setup HTTPS with Let's Encrypt

  1. Install Certbot
  2. Obtain certificate for domain
  3. Configure Nginx with HTTPS
  4. Test certificate

Exercise 2: Implement HTTP to HTTPS Redirect

  1. Set up HTTP server (port 80)
  2. Set up HTTPS server (port 443)
  3. Configure redirect from HTTP → HTTPS
  4. Test redirect

Exercise 3: Enable HSTS

  1. Add HSTS header
  2. Test with browser
  3. Check HSTS preload requirements
  4. (Optional) Submit to HSTS preload list

Exercise 4: Configure OCSP Stapling

  1. Enable OCSP stapling
  2. Configure resolvers
  3. Test OCSP response
  4. Verify with SSL Labs

Exercise 5: Enable HTTP/2

  1. Add http2 parameter to listen directive
  2. Test HTTP/2 connection
  3. Implement HTTP/2 push
  4. Benchmark HTTP/1.1 vs HTTP/2

Exercise 6: Achieve A+ Rating

  1. Configure optimal SSL/TLS settings
  2. Enable all security features
  3. Test with SSL Labs
  4. Fix any issues to achieve A+ rating

11. Best Practices

11.1. Security

# Use strong protocols
ssl_protocols TLSv1.2 TLSv1.3;

Strong ciphers

ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

Disable session tickets

ssl_session_tickets off;

Enable OCSP Stapling

ssl_stapling on; ssl_stapling_verify on;

HSTS

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

11.2. Performance

# Session cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

HTTP/2

listen 443 ssl http2;

Compression

gzip on; gzip_types text/plain text/css application/json application/javascript;

Cache static assets

location ~* .(jpg|png|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; }

11.3. Maintenance

# Regular certificate renewal
sudo certbot renew

Check certificate expiry

sudo certbot certificates

Monitor logs

sudo tail -f /var/log/letsencrypt/letsencrypt.log

Backup certificates

sudo tar -czf letsencrypt-backup.tar.gz /etc/letsencrypt/

11.4. Monitoring

# Monitor SSL Labs rating

Set up automated checks

Monitor certificate expiry

Alert 30 days before expiry

Monitor OCSP stapling

Check periodically

Check security headers

Automated testing


Summary

In this lesson, you learned:

  • ✅ Setting up SSL certificates with Let's Encrypt
  • ✅ HTTP to HTTPS redirects
  • ✅ SSL protocols and cipher optimization
  • ✅ HSTS configuration and preload
  • ✅ OCSP Stapling for better performance
  • ✅ HTTP/2 configuration and optimization
  • ✅ Security headers and best practices
  • ✅ Testing and troubleshooting

Next lesson: We will explore Performance Tuning — worker processes, connections, buffers, timeouts, compression, and caching optimization to maximize Nginx performance.