How to Install SSL/TLS Certificates with Let's Encrypt

Introduction

SSL/TLS certificates are essential for securing websites and protecting user data transmitted over the internet. Let's Encrypt has revolutionized web security by providing free, automated, and open SSL/TLS certificates, making HTTPS accessible to everyone. Founded by the Internet Security Research Group (ISRG) in 2016, Let's Encrypt has issued over 3 billion certificates, dramatically increasing the adoption of HTTPS across the web.

Let's Encrypt uses the ACME (Automated Certificate Management Environment) protocol to verify domain ownership and automatically issue certificates. Certificates are valid for 90 days and can be renewed automatically, ensuring your website remains secure without manual intervention. This approach has removed the financial and technical barriers that previously prevented many websites from implementing SSL/TLS encryption.

This comprehensive guide covers everything you need to know about installing and managing Let's Encrypt SSL/TLS certificates. You'll learn how to use Certbot (the official Let's Encrypt client) to obtain certificates for Apache and Nginx, configure automatic renewal, implement best practices for SSL/TLS security, troubleshoot common issues, and understand certificate management. Whether you're securing a simple blog or a complex multi-domain application, this guide provides the foundation for implementing robust HTTPS encryption.

Prerequisites

Before installing Let's Encrypt certificates, ensure you have:

  • A registered domain name pointing to your server's IP address
  • Apache or Nginx installed and configured on Ubuntu/Debian or CentOS/Rocky Linux
  • Root or sudo access to your server
  • Ports 80 (HTTP) and 443 (HTTPS) open in your firewall
  • A stable internet connection
  • Valid DNS records (A or AAAA records) pointing to your server
  • Your web server configured with a virtual host/server block for your domain
  • Basic understanding of SSL/TLS concepts
  • No other services using port 80 during certificate issuance

Understanding Let's Encrypt

How Let's Encrypt Works

Certificate Issuance Process:
1. Certbot requests certificate from Let's Encrypt CA
2. Let's Encrypt generates challenge to verify domain ownership
3. Certbot places challenge file in .well-known/acme-challenge/
4. Let's Encrypt verifies challenge by accessing file via HTTP
5. Upon successful verification, certificate is issued
6. Certbot installs certificate and configures web server

Certificate Validation Methods

HTTP-01 Challenge:

  • Most common method
  • Requires port 80 accessible
  • Cannot be used for wildcard certificates
  • Verifies domain ownership via HTTP

DNS-01 Challenge:

  • Used for wildcard certificates
  • Requires DNS API access
  • Can validate internal domains
  • More complex setup

TLS-ALPN-01 Challenge:

  • Uses port 443
  • Good for firewall-restricted environments
  • Not as widely supported

Certificate Characteristics

  • Validity Period: 90 days
  • Renewal: Recommended at 60 days
  • Domain Validation (DV): Verifies domain ownership only
  • Certificate Types: Single domain, multiple domains (SAN), wildcard
  • Encryption: RSA 2048-bit or ECDSA keys
  • Browser Compatibility: All modern browsers
  • Cost: Completely free

Installing Certbot

Certbot is the official Let's Encrypt client that automates certificate management.

Ubuntu 20.04/22.04/24.04 and Debian 10/11/12

# Update package repository
sudo apt update

# Install Certbot
sudo apt install certbot -y

# Install Certbot plugin for Apache
sudo apt install python3-certbot-apache -y

# Or install Certbot plugin for Nginx
sudo apt install python3-certbot-nginx -y

# Verify installation
certbot --version

CentOS 8, Rocky Linux, and AlmaLinux

# Enable EPEL repository
sudo dnf install epel-release -y

# Install Certbot
sudo dnf install certbot -y

# Install Certbot plugin for Apache
sudo dnf install python3-certbot-apache -y

# Or install Certbot plugin for Nginx
sudo dnf install python3-certbot-nginx -y

# Verify installation
certbot --version

CentOS 7

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

# Install Certbot
sudo yum install certbot -y

# Install Certbot plugin
sudo yum install python2-certbot-apache -y  # For Apache
sudo yum install python2-certbot-nginx -y   # For Nginx

# Verify installation
certbot --version

Alternative: Snap Installation (Recommended by Let's Encrypt)

# Install snapd
sudo apt install snapd -y  # Ubuntu/Debian
sudo dnf install snapd -y  # CentOS/Rocky

# Ensure snapd is up to date
sudo snap install core
sudo snap refresh core

# Remove any existing Certbot packages
sudo apt remove certbot -y  # Ubuntu/Debian
sudo dnf remove certbot -y  # CentOS/Rocky

# Install Certbot via snap
sudo snap install --classic certbot

# Create symbolic link
sudo ln -s /snap/bin/certbot /usr/bin/certbot

# Verify installation
certbot --version

Obtaining Certificates for Apache

Automatic Configuration

Certbot can automatically configure Apache:

# Obtain and install certificate with automatic configuration
sudo certbot --apache

# For specific domain
sudo certbot --apache -d example.com -d www.example.com

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

During the process, Certbot will:

  1. Detect your Apache configuration
  2. Verify domain ownership
  3. Obtain the certificate
  4. Configure Apache virtual hosts
  5. Enable SSL module
  6. Set up HTTP to HTTPS redirect (optional)

Manual Certificate Only (Webroot)

Obtain certificate without modifying Apache configuration:

# Specify webroot for verification
sudo certbot certonly --webroot -w /var/www/example.com/html -d example.com -d www.example.com

# The certificate files will be saved in:
# /etc/letsencrypt/live/example.com/

Standalone Mode

Use standalone mode if Apache isn't running:

# Stop Apache temporarily
sudo systemctl stop apache2  # Ubuntu/Debian
sudo systemctl stop httpd    # CentOS/Rocky

# Obtain certificate
sudo certbot certonly --standalone -d example.com -d www.example.com

# Start Apache
sudo systemctl start apache2  # Ubuntu/Debian
sudo systemctl start httpd    # CentOS/Rocky

Manual Apache Configuration

After obtaining certificate with certonly, configure Apache manually:

Ubuntu/Debian

Create or edit virtual host:

sudo nano /etc/apache2/sites-available/example.com-ssl.conf

Add configuration:

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin [email protected]

    DocumentRoot /var/www/example.com/html

    # SSL Engine
    SSLEngine on

    # Certificate files
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    # Strong SSL configuration
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder off
    SSLSessionTickets off

    # HSTS Header
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    <Directory /var/www/example.com/html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/example-ssl-access.log combined
</VirtualHost>

# HTTP to HTTPS redirect
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

Enable configuration:

# Enable SSL module
sudo a2enmod ssl

# Enable site
sudo a2ensite example.com-ssl.conf

# Test configuration
sudo apache2ctl configtest

# Reload Apache
sudo systemctl reload apache2

CentOS/Rocky/AlmaLinux

Edit virtual host:

sudo nano /etc/httpd/conf.d/example.com-ssl.conf

Add configuration:

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/html

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite HIGH:!aNULL:!MD5

    <Directory /var/www/example.com/html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

Test and reload:

sudo httpd -t
sudo systemctl reload httpd

Obtaining Certificates for Nginx

Automatic Configuration

Certbot can automatically configure Nginx:

# Obtain and install certificate with automatic configuration
sudo certbot --nginx

# For specific domain
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

Certbot will:

  1. Detect Nginx server blocks
  2. Verify domain ownership
  3. Obtain the certificate
  4. Configure Nginx server blocks for HTTPS
  5. Set up HTTP to HTTPS redirect

Manual Certificate Only (Webroot)

# Specify webroot for verification
sudo certbot certonly --webroot -w /var/www/example.com/html -d example.com -d www.example.com

# Certificates saved in:
# /etc/letsencrypt/live/example.com/

Standalone Mode

# Stop Nginx temporarily
sudo systemctl stop nginx

# Obtain certificate
sudo certbot certonly --standalone -d example.com -d www.example.com

# Start Nginx
sudo systemctl start nginx

Manual Nginx Configuration

After obtaining certificate with certonly, configure Nginx manually:

Ubuntu/Debian

Create or edit server block:

sudo nano /etc/nginx/sites-available/example.com

Add configuration:

# HTTPS server block
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com www.example.com;

    root /var/www/example.com/html;
    index index.html index.htm;

    # 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_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # SSL session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    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;

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

    # Let's Encrypt renewal location
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/example.com/html;
    }
}

# HTTP to HTTPS redirect
server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    # Allow Let's Encrypt validation
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/example.com/html;
    }

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

Enable and reload:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

CentOS/Rocky/AlmaLinux

Edit server block:

sudo nano /etc/nginx/conf.d/example.com.conf

Use the same configuration as above, then:

sudo nginx -t
sudo systemctl reload nginx

Wildcard Certificates

Wildcard certificates secure a domain and all its subdomains:

DNS Challenge Required

Wildcard certificates require DNS validation:

# Install DNS plugin (example for Cloudflare)
sudo apt install python3-certbot-dns-cloudflare -y

# Create credentials file
sudo nano /etc/letsencrypt/cloudflare.ini

Add Cloudflare API token:

dns_cloudflare_api_token = YOUR_API_TOKEN_HERE

Secure the file:

sudo chmod 600 /etc/letsencrypt/cloudflare.ini

Obtain wildcard certificate:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d example.com \
  -d '*.example.com'

Popular DNS Plugins

# Cloudflare
sudo apt install python3-certbot-dns-cloudflare

# DigitalOcean
sudo apt install python3-certbot-dns-digitalocean

# Route53 (AWS)
sudo apt install python3-certbot-dns-route53

# Google Cloud DNS
sudo apt install python3-certbot-dns-google

Automatic Renewal

Testing Renewal

Test the renewal process without actually renewing:

sudo certbot renew --dry-run

This simulates renewal and identifies potential issues.

Automatic Renewal Setup

Certbot automatically installs renewal timer/cron job:

Systemd Timer (Ubuntu 20.04+, CentOS 8+)

Check timer status:

sudo systemctl list-timers | grep certbot
sudo systemctl status certbot.timer

Enable timer if not active:

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

View timer configuration:

sudo cat /lib/systemd/system/certbot.timer

Cron Job (Older Systems)

Check cron job:

sudo cat /etc/cron.d/certbot

Typical content:

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew

Manual Renewal

Force renewal manually:

# Renew all certificates
sudo certbot renew

# Renew specific certificate
sudo certbot renew --cert-name example.com

# Force renewal even if not due
sudo certbot renew --force-renewal

Post-Renewal Hooks

Execute commands after successful renewal:

# Reload web server after renewal
sudo certbot renew --deploy-hook "systemctl reload apache2"  # Apache
sudo certbot renew --deploy-hook "systemctl reload nginx"   # Nginx

Create permanent hook:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh

Add:

#!/bin/bash
systemctl reload nginx

Make executable:

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

Certificate Management

List Certificates

sudo certbot certificates

Output shows:

  • Certificate name
  • Domains covered
  • Expiry date
  • Certificate path
  • Private key path

Expand Certificate (Add Domains)

# Add new domain to existing certificate
sudo certbot --expand -d example.com -d www.example.com -d blog.example.com

Delete Certificate

# Delete certificate
sudo certbot delete --cert-name example.com

# Interactive deletion
sudo certbot delete

Revoke Certificate

# Revoke and delete certificate
sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem --delete-after-revoke

Certificate Files Structure

/etc/letsencrypt/
├── live/
│   └── example.com/
│       ├── cert.pem          # Domain certificate only
│       ├── chain.pem         # Intermediate certificates
│       ├── fullchain.pem     # cert.pem + chain.pem (use this for web servers)
│       └── privkey.pem       # Private key (keep secure!)
├── archive/
│   └── example.com/          # All historical certificates
├── renewal/
│   └── example.com.conf      # Renewal configuration
└── accounts/                 # Let's Encrypt account info

Security Best Practices

Strong SSL/TLS Configuration

Apache

# Enable modern SSL protocols only
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

# Strong cipher suites
SSLCipherSuite 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

# Server cipher preference
SSLHonorCipherOrder off

# OCSP Stapling
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

# HSTS Header
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Nginx

# SSL 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;
ssl_prefer_server_ciphers off;

# SSL session settings
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
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;

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

File Permissions

Ensure proper permissions:

# Let's Encrypt directories should be owned by root
sudo chown -R root:root /etc/letsencrypt
sudo chmod -R 755 /etc/letsencrypt/live
sudo chmod -R 755 /etc/letsencrypt/archive

# Private keys should be readable only by root
sudo chmod 600 /etc/letsencrypt/archive/*/privkey*.pem

Test SSL Configuration

Use online tools:

Or command line:

# Test SSL/TLS
curl -I https://example.com

# Check certificate details
openssl s_client -connect example.com:443 -servername example.com < /dev/null

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

Troubleshooting

Certificate Validation Failures

Issue: "Failed authorization procedure"

Check DNS:

dig example.com
nslookup example.com

Ensure domain points to your server's IP.

Issue: "Connection refused"

Verify web server is running:

sudo systemctl status apache2  # or nginx
sudo ss -tulpn | grep :80

Check firewall:

sudo ufw status  # Ubuntu/Debian
sudo firewall-cmd --list-all  # CentOS/Rocky

Renewal Failures

Check renewal configuration:

sudo cat /etc/letsencrypt/renewal/example.com.conf

Test renewal:

sudo certbot renew --dry-run --cert-name example.com

Check logs:

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

Rate Limits

Let's Encrypt has rate limits:

  • 50 certificates per registered domain per week
  • 5 duplicate certificates per week
  • 300 new orders per account per 3 hours

If hit, wait for the limit period to expire.

Certificate Not Trusted

Ensure using fullchain.pem:

# Correct
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem

# Incorrect
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
# Correct
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;

# Incorrect
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;

Monitoring Certificate Expiry

Check Expiry Date

# Check all certificates
sudo certbot certificates

# Check specific certificate expiry
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate

Monitoring Script

Create monitoring script:

sudo nano /usr/local/bin/check-cert-expiry.sh

Add:

#!/bin/bash
DOMAIN="example.com"
EXPIRY=$(sudo certbot certificates | grep -A 3 "$DOMAIN" | grep "Expiry Date" | awk '{print $3}')
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))

if [ $DAYS_LEFT -lt 30 ]; then
    echo "WARNING: Certificate for $DOMAIN expires in $DAYS_LEFT days!"
    # Send email alert
    echo "Certificate expires in $DAYS_LEFT days" | mail -s "SSL Certificate Warning" [email protected]
fi

Make executable:

sudo chmod +x /usr/local/bin/check-cert-expiry.sh

Add to cron:

sudo crontab -e

Add:

0 0 * * * /usr/local/bin/check-cert-expiry.sh

Best Practices

Regular Monitoring

  • Check certificate expiry regularly
  • Monitor renewal logs
  • Test renewals with --dry-run
  • Keep Certbot updated

Update Certbot

# Ubuntu/Debian (APT)
sudo apt update
sudo apt upgrade certbot python3-certbot-nginx python3-certbot-apache

# Snap
sudo snap refresh certbot

Backup Certificates

# Backup Let's Encrypt directory
sudo tar -czf letsencrypt-backup-$(date +%F).tar.gz /etc/letsencrypt/

# Store backup securely off-server

Documentation

Document your certificates:

  • Domain names covered
  • Certificate type (single, SAN, wildcard)
  • Renewal method
  • Deployment hooks
  • Contact information

Conclusion

Let's Encrypt has made SSL/TLS encryption accessible to everyone, removing cost and complexity barriers. This guide has covered installation, certificate obtainment for Apache and Nginx, automatic renewal, security best practices, and troubleshooting.

Key takeaways:

  • Let's Encrypt provides free, automated SSL/TLS certificates
  • Certbot simplifies certificate management across web servers
  • Automatic renewal ensures continuous security without manual intervention
  • Proper SSL/TLS configuration is essential for maximum security
  • Regular monitoring and testing prevent certificate expiry issues

Implement HTTPS on all your websites to protect user data, improve SEO rankings, and build trust with visitors. Continue exploring advanced topics like wildcard certificates, multi-domain certificates, CAA DNS records, and certificate transparency monitoring. Always use strong SSL/TLS configurations and keep your certificates up to date for optimal security.