When setting up HTTPS with Nginx and Let's Encrypt, you might encounter a situation where your server returns HTTP 400 errors for all HTTPS requests, while the logs show garbled binary data like:
mysite_nginx | 1.1.1.1 - - [04/Apr/2019:16:43:52 +0000] "\\x16\\x03\\x01\\x00\\xC6\\x01\\x00\\x00\\xC2\\x03\\x03\\x97\\x08D\\x08\\x87\\x5Cg\\xDB\\x85\\x8Ch\\x16\\xC9\\x1E\\x01\\xDB\\x9F\\x12\\x04\\x91e\\xB3P]4]\\xFE\\xEF\\xE5^\\xB7\\x07\\x00\\x00\\x1C" 400 157 "-" "-" "-"
The binary-looking output in logs suggests Nginx is receiving what it thinks is malformed HTTP traffic on the SSL port. This typically happens when:
- The SSL configuration has errors
- Port forwarding isn't set up correctly in Docker
- SSL certificate paths are incorrect
- Protocol mismatch occurs between client and server
Looking at the provided configuration, there are several areas to examine:
server {
listen 443 ssl; # Explicit SSL declaration is important
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ... rest of configuration
}
The most obvious issue in the original setup is the missing ssl
parameter in the listen directive:
# Wrong:
listen 443;
# Correct:
listen 443 ssl;
When running Nginx in Docker with SSL, you need to ensure:
- Ports are correctly exposed in docker-compose.yml
- Certificate paths match the container filesystem
- Nginx has proper permissions to access certificate files
Here's an improved docker-compose snippet:
nginx:
image: nginx:1.15.9-alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./sites-enabled:/etc/nginx/conf.d
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
ports:
- "80:80"
- "443:443"
command: "/bin/sh -c 'nginx -g \"daemon off;\"'"
Here's a fully functional Nginx configuration for HTTPS:
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
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;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://app:8080;
include /etc/nginx/proxy_params;
}
}
If you're still seeing 400 errors after making these changes:
- Verify certificate files exist in the container:
docker exec -it nginx_container ls -la /etc/letsencrypt/live/example.com/
- Check Nginx configuration syntax:
docker exec -it nginx_container nginx -t
- Inspect SSL handshake with OpenSSL:
openssl s_client -connect example.com:443 -servername example.com
For production environments, consider adding these SSL optimizations:
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
When setting up HTTPS with Nginx and Let's Encrypt, receiving a 400 Bad Request error for HTTPS connections typically indicates a fundamental SSL/TLS configuration issue. The error logs showing garbled characters (\x16\x03\x01...
) suggest the server isn't properly handling the SSL handshake.
Let's examine the key components of a working Nginx SSL configuration:
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 /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Other location blocks...
}
The Docker setup introduces several potential failure points:
- Volume mounts not having correct permissions
- Certificate files not being read properly
- Missing
ssl
parameter in listen directive
Before making configuration changes, verify these aspects:
- Certificate files exist and are accessible:
docker exec container_name ls -la /etc/letsencrypt/live/example.com/
- Check Nginx configuration syntax:
docker exec container_name nginx -t
Here's a proven configuration that works with Docker and Let's Encrypt:
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://app:8000;
include /etc/nginx/proxy_params;
}
location /static/ {
alias /static/;
expires 30d;
access_log off;
}
}
Use these commands to diagnose SSL issues:
openssl s_client -connect example.com:443 -showcerts
curl -vI https://example.com