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:
- Detect your Apache configuration
- Verify domain ownership
- Obtain the certificate
- Configure Apache virtual hosts
- Enable SSL module
- 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:
- Detect Nginx server blocks
- Verify domain ownership
- Obtain the certificate
- Configure Nginx server blocks for HTTPS
- 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:
- SSL Labs: https://www.ssllabs.com/ssltest/
- Mozilla Observatory: https://observatory.mozilla.org/
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.


