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 updateInstall Certbot
sudo apt install certbot python3-certbot-nginx -y
Verify installation
certbot --version
CentOS/RHEL:
# Install EPEL repository sudo yum install epel-release -yInstall 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 certbotNginx 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.comFollow prompts:
- Enter email address
- Agree to terms
- Choose: redirect HTTP to HTTPS (recommended)
Certbot will:
- Verify domain ownership
- Obtain certificate
- Automatically configure Nginx
- Setup auto-renewal
Check certificate:
# List certificates sudo certbot certificatesOutput:
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.comOr 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 renewRenew 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.timerEnable 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.comFollow 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.comOr 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 2048Or 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:
- Visit https://hstspreload.org/
- Enter your domain
- 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 LabsVisit: 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.comLook 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 clientserver { 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.confuser 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.comHTTP - 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 Labshttps://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_2Test 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/nullBenchmark 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.comShould 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 -showcertsVerify 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"></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 nginxWait 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.comShould 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 nginxUse 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
- Install Certbot
- Obtain certificate for domain
- Configure Nginx with HTTPS
- Test certificate
Exercise 2: Implement HTTP to HTTPS Redirect
- Set up HTTP server (port 80)
- Set up HTTPS server (port 443)
- Configure redirect from HTTP → HTTPS
- Test redirect
Exercise 3: Enable HSTS
- Add HSTS header
- Test with browser
- Check HSTS preload requirements
- (Optional) Submit to HSTS preload list
Exercise 4: Configure OCSP Stapling
- Enable OCSP stapling
- Configure resolvers
- Test OCSP response
- Verify with SSL Labs
Exercise 5: Enable HTTP/2
- Add http2 parameter to listen directive
- Test HTTP/2 connection
- Implement HTTP/2 push
- Benchmark HTTP/1.1 vs HTTP/2
Exercise 6: Achieve A+ Rating
- Configure optimal SSL/TLS settings
- Enable all security features
- Test with SSL Labs
- 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 renewCheck 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 ratingSet 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.