The Complete NGINX on Ubuntu Series: Part 5 – SSL Certificates with Let’s Encrypt and Certbot

The Complete NGINX on Ubuntu Series: Part 5 – SSL Certificates with Let’s Encrypt and Certbot

Welcome to Part 5 of our comprehensive NGINX on Ubuntu series! We’ve set up static sites, virtual hosts, and PHP applications. Now it’s time to secure everything with SSL certificates using Let’s Encrypt and Certbot for free, automated HTTPS.

Understanding SSL/TLS and HTTPS

SSL/TLS certificates encrypt data between your server and clients, providing security, trust, and improved SEO rankings. Let’s Encrypt provides free certificates with automated renewal.

graph TD
    A[Client Browser] --> B[NGINX Server :443]
    B --> C[SSL Certificate Check]
    C --> D{Certificate Valid?}
    
    D -->|Yes| E[Establish TLS Connection]
    D -->|No| F[Show Security Warning]
    
    E --> G[Encrypted Communication]
    G --> H[Serve Content via HTTPS]
    
    I[Let's Encrypt CA] --> J[Issues Certificate]
    J --> K[Certbot on Server]
    K --> L[Auto-renewal every 60 days]
    
    style B fill:#e1f5fe
    style C fill:#e8f5e8
    style G fill:#e8f5e8
    style I fill:#fff3e0

Installing Certbot

Certbot is the official Let’s Encrypt client that automates certificate issuance and renewal.

# Update package index
sudo apt update

# Install Certbot and NGINX plugin
sudo apt install certbot python3-certbot-nginx -y

# Verify installation
certbot --version

Preparing Domain and DNS

Before obtaining certificates, ensure your domain points to your server’s public IP address.

# Check your server's public IP
curl -4 icanhazip.com

# Verify DNS resolution (replace with your domain)
nslookup example.com
dig example.com A

# Test HTTP access (should work before SSL)
curl -I http://example.com

Obtaining SSL Certificates

Method 1: Automatic NGINX Configuration

# Obtain certificate and auto-configure NGINX
sudo certbot --nginx -d example.com -d www.example.com

# For multiple domains
sudo certbot --nginx -d example.com -d www.example.com -d blog.example.com -d api.example.com

Method 2: Certificate Only (Manual Configuration)

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

# Alternative: Use webroot method
sudo certbot certonly --webroot -w /var/www/example.com/public_html -d example.com -d www.example.com

Manual NGINX SSL Configuration

If using certonly, configure NGINX manually for better control over SSL settings.

# Edit your virtual host
sudo nano /etc/nginx/sites-available/example.com
# HTTP to HTTPS 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;
}

# HTTPS server block
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;
    
    root /var/www/example.com/public_html;
    index index.html index.htm index.php;
    
    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # SSL Security Settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_dhparam /etc/nginx/dhparam.pem;
    
    # SSL Session Configuration
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    
    # Security Headers
    add_header Strict-Transport-Security "max-age=63072000; 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;
    
    # Main location block
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    # PHP processing (if needed)
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    
    # Static file optimization
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # Security
    location ~ /\. { deny all; }
    
    # Logs
    access_log /var/www/example.com/logs/access.log;
    error_log /var/www/example.com/logs/error.log;
}

Generating Strong Diffie-Hellman Parameters

# Generate strong DH parameters (takes several minutes)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

# Verify the file was created
ls -la /etc/nginx/dhparam.pem

Testing SSL Configuration

# Test NGINX configuration
sudo nginx -t

# Reload NGINX
sudo systemctl reload nginx

# Test SSL certificate
openssl s_client -connect example.com:443 -servername example.com

# Check certificate details
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# Test HTTP to HTTPS redirect
curl -I http://example.com

Automatic Certificate Renewal

Let’s Encrypt certificates expire every 90 days. Certbot automatically sets up renewal.

graph TD
    A[Certificate Issued] --> B[Valid for 90 days]
    B --> C[Certbot checks daily]
    C --> D{30 days left?}
    
    D -->|No| E[Continue monitoring]
    D -->|Yes| F[Attempt renewal]
    
    F --> G{Renewal successful?}
    G -->|Yes| H[Reload NGINX]
    G -->|No| I[Log error, retry]
    
    H --> J[Certificate renewed]
    I --> F
    E --> C
    J --> B
    
    style A fill:#e8f5e8
    style F fill:#fff3e0
    style H fill:#e1f5fe
    style I fill:#ffebee
# Check renewal configuration
sudo certbot renew --dry-run

# View renewal timer
sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot

# Manual renewal test
sudo certbot renew --force-renewal

# Check certificate status
sudo certbot certificates

Configuring Multiple Domains

Separate Certificates for Each Domain

# Individual certificates
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot --nginx -d blog.example.com
sudo certbot --nginx -d api.example.com

Wildcard Certificates

# Wildcard certificate (requires DNS validation)
sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d example.com

# Follow DNS TXT record instructions
# Add TXT record to your DNS: _acme-challenge.example.com

SSL Security Best Practices

Create SSL Configuration Snippet

# Create reusable SSL configuration
sudo nano /etc/nginx/snippets/ssl-params.conf
# SSL Security Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;

# Session Configuration
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# Security Headers
add_header Strict-Transport-Security "max-age=63072000; 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;

Use SSL Snippet in Virtual Hosts

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;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    
    # Include SSL security settings
    include /etc/nginx/snippets/ssl-params.conf;
    
    # Rest of configuration...
}

Monitoring and Maintenance

# Check certificate expiry
sudo certbot certificates

# View renewal logs
sudo journalctl -u certbot

# Test SSL grade
curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com" | jq

# Monitor renewal attempts
sudo tail -f /var/log/letsencrypt/letsencrypt.log

# Force renewal for testing
sudo certbot renew --cert-name example.com --force-renewal

Troubleshooting SSL Issues

Common Certificate Issues

# Certificate not found
sudo ls -la /etc/letsencrypt/live/

# Check NGINX error logs
sudo tail -f /var/log/nginx/error.log

# Test certificate chain
openssl verify -CAfile /etc/letsencrypt/live/example.com/chain.pem /etc/letsencrypt/live/example.com/cert.pem

# Debug SSL handshake
openssl s_client -connect example.com:443 -servername example.com -debug

DNS Validation Issues

# Check DNS propagation
dig example.com A
nslookup example.com

# For wildcard certificates, check TXT records
dig _acme-challenge.example.com TXT

What’s Next?

Excellent! You’ve successfully secured your NGINX server with SSL certificates from Let’s Encrypt. Your websites now serve traffic over HTTPS with automatic certificate renewal.

Coming up in Part 6: NGINX as a Reverse Proxy for Backend Applications

References


This is Part 5 of our 22-part NGINX series. Your websites are now secure with HTTPS! Next, we’ll explore reverse proxy configurations. Questions about SSL setup? Share them in the comments!

Written by:

472 Posts

View All Posts
Follow Me :