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

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.

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

Nginx từ Cơ bản đến Nâng cao

Phần 3: Security & Performance

xdev.asia

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:


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"&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

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.