Debugging Nginx SSL Handshake Failures with Lets Encrypt on AWS EC2 Behind an ELB
- Get link
- X
- Other Apps
Debugging Nginx SSL Handshake Failures with Let's Encrypt on AWS EC2 Behind an ELB
Navigating SSL/TLS handshake failures can be a daunting task, especially when dealing with a multi-layered infrastructure involving Nginx, Let's Encrypt, and AWS Elastic Load Balancers (ELB). This comprehensive guide provides a structured approach for diagnosing and resolving common SSL handshake issues, ensuring your web services remain secure and accessible. We'll dive deep into common symptoms, root causes, and provide step-by-step commands to get your secure connections back online.
Symptom Analysis & Root Causes
Understanding the symptoms is the first step towards an effective diagnosis. SSL handshake failures typically manifest in various ways depending on the client and the specific error.
Common Symptoms
- Browser Errors: Clients encounter errors like
ERR_SSL_PROTOCOL_ERROR,SSL_HANDSHAKE_FAILED,NET::ERR_CERT_COMMON_NAME_INVALID, or warnings about untrusted certificates. curlorwgetFailures: Command-line tools fail to connect with errors such as "SSL handshake failed", "certificate verify failed", or "unable to get local issuer certificate".- Nginx Error Logs: Nginx logs (typically found at
/var/log/nginx/error.log) may show "SSL_do_handshake() failed", "no suitable certificate found", or other related SSL errors. - Application Connectivity Issues: Backend applications relying on SSL may fail to connect, indicating an underlying certificate or handshake problem.
Underlying Root Causes
SSL handshake failures in an AWS ELB + EC2 + Nginx + Let's Encrypt setup usually stem from misconfigurations at one of these layers:
- AWS Elastic Load Balancer (ELB) Configuration:
- Incorrect Listener Configuration: ELB not listening on port 443 (HTTPS) or forwarding to the wrong port on the EC2 instance (e.g., forwarding HTTPS to HTTP).
- Target Group Issues: Health checks failing, causing ELB to mark instances as unhealthy.
- ELB Security Group: Inbound rules on the ELB's security group not allowing traffic on port 443.
- EC2 Security Group & Instance Firewall:
- EC2 Security Group: Inbound rules not allowing traffic from the ELB (or 0.0.0.0/0 for testing) on port 443 (or the port Nginx is listening on for SSL).
- Instance Firewall: Local firewall on the EC2 instance (e.g.,
ufw,firewalld,iptables) blocking port 443.
- Nginx Configuration (
nginx.conf):- Incorrect
ssl_certificateorssl_certificate_keypaths. - Missing
ssl_trusted_certificate(fullchain.pem) which provides the intermediate certificates. - Outdated or weak
ssl_protocolsorssl_cipherssettings, causing incompatibility with clients. - Incorrect
server_namedirective for the SSL block. - Issues with HTTP/2 configuration (
http2directive). - Not enabling
proxy_protocolif the ELB is configured to send it (typically for TCP listeners forwarding to an Nginx that handles SSL).
- Incorrect
- Let's Encrypt Certificates:
- Expired Certificates: Certificates not renewed automatically by Certbot.
- Incomplete Certificate Chain: Nginx configured with only the domain certificate, not the full chain (including intermediate CA certificates).
- Permissions Issues: Nginx unable to read certificate files due to incorrect file permissions.
- Wrong Domain: Certificate issued for a different domain name than the one Nginx is serving.
- DNS Issues: Domain not resolving to the ELB's public DNS or IP address.
Step-by-Step Resolution Guide
Follow these steps systematically to pinpoint and resolve your SSL handshake issues.
Step 1: Verify AWS ELB Configuration
Start by ensuring your ELB is correctly configured to receive and forward HTTPS traffic. This guide assumes your ELB is set up with a TCP listener on port 443, forwarding traffic to the EC2 instance's port 443, where Nginx handles SSL termination. If your ELB terminates SSL, Nginx would typically listen on HTTP (port 80) and this guide's focus shifts slightly (but many Nginx configs for SSL still apply internally).
- Check ELB Listener: Navigate to EC2 > Load Balancers in the AWS console. Select your ELB and check its "Listeners" tab. Ensure there's a listener for
TCP:443forwarding to a target group onTCP:443. - Target Group Health Checks: In the "Target Groups" section, select your target group and check the "Health checks" tab. Ensure the health check path and port are correct (e.g., HTTP on port 80 or 443 if you have an internal SSL setup for health checks). Instances must be "healthy".
- ELB Security Group: Check the security group associated with your ELB. It must have an inbound rule allowing
TCP:443from0.0.0.0/0(or specific client IPs if restricted).
Step 2: Check EC2 Security Group and Instance Firewall
Ensure traffic can reach your EC2 instance on the required ports.
- EC2 Security Group: Go to EC2 > Instances, select your instance, and check its "Security" tab. The associated security group must have an inbound rule allowing
TCP:443from the ELB's security group or0.0.0.0/0(for testing, restrict later). Also, ensureTCP:80is open if you use HTTP for Certbot challenges (http-01). - Instance Firewall (e.g., UFW/Firewalld): SSH into your EC2 instance and check its local firewall status.
Step 3: Inspect Nginx Configuration
Misconfigured Nginx SSL directives are a frequent culprit.
- Locate Nginx Configuration: Nginx configurations are typically in
/etc/nginx/nginx.confand files included from/etc/nginx/sites-available/or/etc/nginx/conf.d/. - Verify SSL Block: Look for the
serverblock listening on port 443.
- Check File Paths: Ensure
ssl_certificateandssl_certificate_keypaths point to valid Let's Encrypt files. Let's Encrypt typically creates symlinks in/etc/letsencrypt/live/yourdomain.com/. - Full Chain: Always use
fullchain.pemforssl_certificate. This includes your domain certificate and intermediate certificates. Some older clients might also benefit fromssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;if OCSP stapling is configured. - Syntax Check: Test your Nginx configuration for syntax errors.
If there are errors, fix them based on the output. A successful check will return syntax is ok and configuration file /etc/nginx/nginx.conf test is successful.
Step 4: Validate Let's Encrypt Certificate
Ensure your Let's Encrypt certificates are valid, up-to-date, and accessible.
- Check Certificate Expiry: Use Certbot to list your certificates and their expiry dates.
- Manually Renew (if needed): If a certificate is expired or near expiration, attempt a dry run renewal first.
- Check File Permissions: Ensure Nginx has read permissions for the certificate files and directories. The
livedirectory and its contents are typically symlinked, and the actual certs are in/etc/letsencrypt/archive/. Nginx usually runs as thenginxorwww-datauser.
Step 5: Test SSL/TLS Handshake from Client Perspective
Use curl or openssl from a client machine (or your EC2 instance if allowed by firewall rules to connect to itself, or to the ELB DNS) to simulate a client connection.
Errors like "SSL certificate problem: unable to get local issuer certificate" usually indicate an incomplete certificate chain provided by Nginx (e.g., missing fullchain.pem).
Step 6: Common Nginx SSL Directives Issues
ssl_protocolsandssl_ciphers: Ensure you are using modern, secure protocols (TLSv1.2 TLSv1.3) and strong ciphers. Remove older, insecure options likeSSLv3,TLSv1,TLSv1.1.server_nameMismatch: Theserver_namein your Nginx SSL block must match the domain name in your Let's Encrypt certificate.http2Directive: If using HTTP/2, ensure your Nginx version supports it and thehttp2directive is present on thelistenline (e.g.,listen 443 ssl http2;).- Proxy Protocol (if applicable): If your ELB is a TCP listener on 443 and configured to use Proxy Protocol (less common but possible), Nginx might need the
proxy_protocoldirective on the listen line (e.g.,listen 443 ssl http2 proxy_protocol;). However, this would typically be a "bad request" error, not an SSL handshake failure.
Step 7: Restart Nginx
After any configuration changes or certificate renewals, always reload or restart Nginx.
Check sudo journalctl -xe | grep nginx or sudo tail -f /var/log/nginx/error.log for any new errors after restarting.
Step 8: Advanced Debugging with OpenSSL
For deeper insights, use openssl s_client to perform a handshake and examine the output.
If Verify return code is not 0 (ok), investigate the error code. Missing intermediate certificates are a common cause.
Best Practices for Prevention & Performance Optimization
Preventing SSL handshake failures is better than reacting to them. Implement these best practices for a robust and performant setup.
- Automate Certbot Renewals: Ensure Certbot is configured to run automatically (e.g., via cron job) and includes a post-hook to reload Nginx:
sudo certbot renew --nginx --post-hook "sudo systemctl reload nginx"
- Use Strong Protocols & Ciphers: Regularly update your Nginx configuration to use only the latest and strongest TLS protocols (
TLSv1.2,TLSv1.3) and ciphers. Use resources like Mozilla SSL Configuration Generator for recommended settings. - Enable OCSP Stapling: This reduces client load times and improves privacy by allowing Nginx to retrieve OCSP responses from the CA and "staple" them to the TLS handshake, instead of clients having to contact the CA directly. Ensure
ssl_trusted_certificateandresolverdirectives are correctly set. - Implement HTTP to HTTPS Redirection: Force all traffic to HTTPS by redirecting HTTP requests to their HTTPS counterparts within Nginx.
server { listen 80; listen [::]:80; server_name yourdomain.com www.yourdomain.com; return 301 https://$host$request_uri; }
- Strict-Transport-Security (HSTS): Add the HSTS header to instruct browsers to always use HTTPS for your domain, even if they explicitly request HTTP. This enhances security and can improve performance on subsequent visits.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- Monitor ELB Health Checks: Configure detailed health checks for your ELB target groups to swiftly identify unhealthy instances and prevent traffic from being routed to them.
- Regularly Audit Configurations: Periodically review your Nginx, EC2 Security Group, and ELB configurations for any deviations from best practices or unintended changes.
Frequently Asked Questions (FAQ)
Q1: Why does my browser show NET::ERR_CERT_COMMON_NAME_INVALID?
This error indicates that the hostname you are trying to access (e.g., www.yourdomain.com) does not match the domain(s) listed on your SSL certificate. This can happen if the certificate was issued for yourdomain.com but not www.yourdomain.com (or vice-versa), or if you're trying to access an IP address directly. Ensure your Let's Encrypt certificate covers all necessary subdomains and aliases, and that your Nginx server_name directive correctly includes them.
Q2: My certificate is renewed, but clients still see an old one. Why?
After Certbot renews a certificate, you must reload or restart Nginx for it to pick up the new certificate files. If you only perform a certbot renew command without a post-hook, Nginx continues to serve the old, expired certificate. Always ensure your renewal process includes a command like sudo systemctl reload nginx. Additionally, some clients might cache old certificate information, though this is less common for full expiry issues.
Q3: What is OCSP stapling and should I use it?
OCSP (Online Certificate Status Protocol) stapling is a feature where the web server (Nginx) retrieves a signed, time-stamped OCSP response from the Certificate Authority (CA) and sends it ("staples" it) along with the certificate during the TLS handshake. This allows the client to verify the certificate's revocation status without having to connect to the CA's OCSP server directly. It significantly improves privacy (CA doesn't see client IP), performance (faster handshakes), and reliability (no reliance on client's connectivity to CA). Yes, you should definitely use it by configuring ssl_stapling on;, ssl_stapling_verify on;, and resolver directives in your Nginx configuration.
- Get link
- X
- Other Apps