Bài 7: SSL/TLS và HTTPS trong NGINX

Bài học về SSL/TLS và HTTPS trong Nginx - cấu hình SSL certificates với Let's Encrypt, HTTP to HTTPS redirect, SSL protocols và ciphers optimization, HSTS, OCSP Stapling và HTTP/2. Hướng dẫn bảo mật kết nối, tối ưu performance và đạt A+ rating trên SSL Labs.

16 min read
Bài 7: SSL/TLS và HTTPS trong NGINX

1. Cấu hình SSL Certificate (Let's Encrypt)

1.1. Giới thiệu SSL/TLS

SSL (Secure Sockets Layer) / TLS (Transport Layer Security) là protocols để encrypt communication giữa client và server.

Tại sao cần HTTPS:

  • Bảo mật dữ liệu (encryption)
  • Xác thực server (authentication)
  • Toàn vẹn dữ liệu (integrity)
  • SEO benefits (Google ranking)
  • Browser trust (không có warning)
  • Required cho HTTP/2
  • Required cho PWA (Progressive Web Apps)

Certificate Authority (CA):

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

1.2. Cài đặt 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 sẽ tự động 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 sẽ:

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

Kiểm tra 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 (will add more later)
    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 sau 90 ngày. Certbot setup 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 và 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

# Wildcard certificate (requires DNS challenge)
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 với 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 và 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 xác định encryption algorithms được sử dụng.

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 tăng cường 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 với 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;
}

CẢNH BÁO: 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 và 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 cải thiện performance significantly với multiplexing, server push, và 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 server to send resources before 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;
    }
}

CẢNH BÁO: HTTP/2 Push có thể làm giảm performance nếu dùng sai. Chỉ 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 và Optimization

8.1. SSL Labs Test

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

# Target: A+ rating

Checklist cho A+ rating:

  • ✅ TLS 1.2 và 1.3 enabled
  • ✅ Strong ciphers
  • ✅ Certificate valid và trusted
  • ✅ HSTS enabled (với 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:

  • https://securityheaders.com
  • https://observatory.mozilla.org

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 và tìm http:// (not https://)
curl https://example.com | grep 'http://'

Fix:

<!-- Bad -->
<script src="http://example.com/js/app.js"></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"></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

# Common errors:

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. Bài tập Thực hành

Bài tập 1: Setup HTTPS với Let's Encrypt

  1. Install Certbot
  2. Obtain certificate cho domain
  3. Configure Nginx với HTTPS
  4. Test certificate

Bài tập 2: Implement HTTP to HTTPS Redirect

  1. Setup HTTP server (port 80)
  2. Setup HTTPS server (port 443)
  3. Configure redirect từ HTTP → HTTPS
  4. Test redirect

Bài tập 3: Enable HSTS

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

Bài tập 4: Configure OCSP Stapling

  1. Enable OCSP stapling
  2. Configure resolvers
  3. Test OCSP response
  4. Verify với SSL Labs

Bài tập 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

Bài tập 6: Achieve A+ Rating

  1. Configure optimal SSL/TLS settings
  2. Enable all security features
  3. Test với SSL Labs
  4. Fix any issues để đạt 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
# Setup automated checks

# Monitor certificate expiry
# Alert 30 days before expiry

# Monitor OCSP stapling
# Check periodically

# Check security headers
# Automated testing

Tổng kết

Trong bài này, bạn đã học:

  • ✅ Setup SSL certificates với Let's Encrypt
  • ✅ HTTP to HTTPS redirects
  • ✅ SSL protocols và ciphers optimization
  • ✅ HSTS configuration và preload
  • ✅ OCSP Stapling cho better performance
  • ✅ HTTP/2 configuration và optimization
  • ✅ Security headers và best practices
  • ✅ Testing và troubleshooting

Bài tiếp theo: Chúng ta sẽ tìm hiểu về Performance Tuning - worker processes, connections, buffers, timeouts, compression và caching optimization để maximize Nginx performance.

Nginx SSL TLS HTTPS LetsEncrypt Certbot HSTS OCSP HTTP2 security

Đánh dấu hoàn thành (Bài 7: SSL/TLS và HTTPS trong NGINX)