Why HTTPS Matters
HTTPS is no longer optional. Every production web server should be serving traffic over TLS. Browsers now flag plain HTTP sites as "Not Secure," which erodes user trust before they even read your content. Google has used HTTPS as a ranking signal since 2014, and its weight has only increased. Beyond SEO, TLS encrypts data in transit -- protecting login credentials, form submissions, cookies, and session tokens from interception on any network between the client and your server.
HTTPS is also a prerequisite for HTTP/2, which delivers measurable performance improvements through multiplexed connections, header compression, and server push. If you want a modern, fast, trustworthy website, TLS is the foundation.
The good news: Let's Encrypt provides free, automated, and widely trusted certificates. There is no reason to run without one.
Prerequisites
Before you begin, make sure you have the following in place:
- A registered domain name with DNS A records pointing to your server's public IP address
- Nginx installed and serving HTTP traffic on port 80 (even a default page is fine)
- Ports 80 and 443 open in your firewall (both are required -- port 80 for domain validation, port 443 for HTTPS)
- SSH or console access to the server with root or sudo privileges
If you are behind a load balancer or CDN, the process differs slightly. This guide assumes Nginx is directly exposed to the internet.
Installing Certbot
Certbot is the official client from the Electronic Frontier Foundation (EFF) for obtaining and managing Let's Encrypt certificates. It handles domain validation, certificate issuance, and can even modify your Nginx configuration automatically (though we will do it manually for full control).
Debian / Ubuntu
sudo apt update sudo apt install certbot python3-certbot-nginx
RHEL / CentOS / Rocky / Alma
sudo dnf install epel-release sudo dnf install certbot python3-certbot-nginx
FreeBSD
pkg install py311-certbot py311-certbot-nginx
If you prefer lighter alternatives, acme.sh is a pure shell implementation with no dependencies, and getssl is another lightweight option written in Bash. Both work well for automated environments where you want minimal footprint.
Obtaining Your Certificate
The webroot method is the recommended approach for servers already running Nginx. It places a temporary validation file in your web root, which Let's Encrypt checks via an HTTP request to verify domain ownership. No downtime required.
certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
Replace /var/www/html with your actual document root, and substitute your domain names. You can include multiple -d flags for additional subdomains -- they will all be covered by a single certificate using Subject Alternative Names (SANs).
When successful, Certbot places your certificate files in /etc/letsencrypt/live/example.com/. The key files are:
fullchain.pem-- your certificate plus the intermediate chain (this is what Nginx needs)privkey.pem-- your private key (keep this secure, never share it)chain.pem-- just the intermediate certificate (used for OCSP stapling)cert.pem-- your domain certificate alone (rarely needed directly)
Nginx SSL Configuration
With your certificate in hand, configure Nginx to serve HTTPS and redirect all HTTP traffic. You need two server blocks: one listening on port 443 for encrypted traffic, and one on port 80 that redirects everything to HTTPS.
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# HTTPS server block
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;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Test the configuration before reloading:
nginx -t nginx -s reload
Visit https://example.com in your browser. You should see the padlock icon indicating a valid TLS connection. If not, check your firewall rules and verify that port 443 is open.
SSL/TLS Best Practices
A working certificate is just the beginning. Proper TLS configuration means selecting the right protocols and ciphers, enabling OCSP stapling, and tuning session parameters. Add the following to your server block or to a shared snippet that you include across all sites:
# TLS protocol versions -- drop anything older than 1.2 ssl_protocols TLSv1.2 TLSv1.3; # Prefer server cipher order for consistent security ssl_prefer_server_ciphers on; # Strong cipher suites 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; # OCSP stapling -- proves certificate validity without client-side lookups ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s; # Session cache for performance ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off;
TLS 1.0 and 1.1 are deprecated and contain known vulnerabilities. Dropping them is not aggressive -- it is expected. All modern browsers support TLS 1.2 at minimum, and TLS 1.3 is preferred for its speed and security improvements. Disabling session tickets prevents potential key reuse attacks when tickets are not properly rotated.
Security Headers
TLS encrypts the connection, but security headers protect against a different class of attacks: clickjacking, MIME-type sniffing, protocol downgrade, and information leakage. Add these headers to your HTTPS server block:
# Security headers add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Referrer-Policy strict-origin-when-cross-origin always;
Strict-Transport-Security (HSTS) tells browsers to only connect via HTTPS for the next two years. Once set, even if a user types http://, the browser upgrades the connection before making a request. The preload directive lets you submit your domain to the HSTS preload list maintained by browser vendors, so the policy applies even on first visit.
X-Content-Type-Options prevents browsers from MIME-sniffing a response away from its declared content type. X-Frame-Options blocks your site from being embedded in iframes, protecting against clickjacking. Referrer-Policy controls how much URL information is sent in the Referer header when users follow links off your site.
Automated Renewal
Let's Encrypt certificates expire every 90 days. This is by design -- shorter lifetimes reduce the window of exposure if a key is compromised. Certbot's renewal process is straightforward and should be automated so you never think about it again.
Test renewal before setting up automation:
certbot renew --dry-run
If the dry run succeeds, set up a cron job to run renewal twice daily. Certbot only renews certificates that are within 30 days of expiration, so running it frequently is safe and costs nothing:
# Renew certificates and reload Nginx on success 0 3,15 * * * certbot renew --quiet --post-hook "nginx -s reload"
The --post-hook flag ensures Nginx picks up the new certificate files only after a successful renewal. Without it, Nginx would continue serving the old certificate until the next manual reload.
If your system uses systemd, you may already have a certbot.timer enabled. Check with systemctl list-timers. On FreeBSD, you can add the renewal command to /etc/periodic.conf or use a standard cron entry.
The recommended practice is to attempt renewal at the 60-day mark, giving you 30 days of buffer if something goes wrong -- DNS issues, firewall changes, or provider outages.
Testing Your Configuration
After configuring SSL, headers, and renewal, verify everything works correctly.
SSL Labs
Run your domain through the Qualys SSL Labs test. With the configuration above, you should achieve an A+ rating. The test checks protocol support, cipher strength, key exchange, certificate chain, and known vulnerabilities like Heartbleed, POODLE, and ROBOT.
Header Verification
curl -I https://example.com
Check the response for your security headers: Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy. If any are missing, verify your Nginx configuration syntax and that you included the always parameter.
Certificate Debugging
openssl s_client -connect example.com:443 -servername example.com
This command shows the full certificate chain, protocol version, cipher suite, and session details. It is the most useful tool for debugging TLS issues -- certificate ordering problems, expired intermediates, and SNI misconfigurations all show up here.
You can also verify the certificate expiration date directly:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
This outputs the notBefore and notAfter dates, confirming your certificate is current and when it will expire.
Need help hardening your server security? Learn about our security services or schedule a consultation.