The Post-Deployment Surprise
You just finished a smooth SSL rollout or migrated your app behind a new load balancer. Everything seems perfect until you check the logs and notice a spike in 400 errors. When a user accidentally types http:// instead of https:// while specifying port 443, Nginx hits them with a blunt error message:
400 Bad Request: The plain HTTP request was sent to HTTPS port
This happens because Nginx is strictly waiting for an encrypted SSL/TLS handshake on port 443. If it receives a standard, unencrypted GET request instead, it doesn't know how to proceed. Rather than guessing your intent, Nginx terminates the connection to protect the secure socket.
The 30-Second Fix
To fix this, you can instruct Nginx to intercept this specific error and redirect the user to the correct secure URL. Add this line inside your server block that handles SSL traffic:
server {
listen 443 ssl;
server_name example.com;
# Redirect internal error 497 to the HTTPS version
error_page 497 https://$host$request_uri;
# ... your remaining SSL configuration
}
After saving the file, apply the changes with nginx -s reload. Nginx uses the internal code 497 specifically for HTTP-to-HTTPS port mismatches. This directive catches that event and performs a 301-style redirect to the secure protocol.
Why Does This Error Occur?
Protocol strictness is the core issue. When you define listen 443 ssl;, Nginx expects the very first byte of the connection to be the start of a TLS handshake. If a legacy script or a manual URL entry sends GET / HTTP/1.1 in plain text to that port, Nginx sees it as garbage data. It cannot upgrade the connection mid-stream, so it triggers the 400 response.
You will typically see this in three specific scenarios:
- **Manual URL Typos:** A user explicitly types `http://yourdomain.com:443` into their browser.
- **Load Balancer Mismatches:** An upstream service like AWS ALB or HAProxy is configured to talk to Nginx via plain HTTP but is targeting Nginx's SSL-enabled port.
- **Hardcoded API Clients:** Older mobile apps or IoT devices might have `http://` hardcoded while trying to reach a service that recently moved to port 443.
Advanced Configuration Methods
Method 1: The error_page 497 Directive (Best Practice)
This is the most efficient approach because it handles the error at the source. It prevents the connection from dropping and preserves the user's specific path and query strings.
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# Handles the plain-text-to-SSL-port mismatch
error_page 497 https://$host$request_uri;
location / {
proxy_pass http://localhost:8080;
}
}
Method 2: Solving Proxy and Load Balancer Loops
If your Nginx instance sits behind a Load Balancer that performs SSL Termination, the setup changes. In this case, the Load Balancer handles the certificate and talks to Nginx over plain HTTP. If Nginx still has ssl enabled on its listening port, it will reject the Load Balancer's traffic.
The Solution: Remove the ssl directive from Nginx and rely on the X-Forwarded-Proto header to determine if the original request was secure.
server {
listen 80; # The LB talks to Nginx on port 80
server_name example.com;
# Trust the internal IP range of your Load Balancer (e.g., 10.0.0.0/8)
real_ip_header X-Forwarded-For;
set_real_ip_from 10.0.0.0/8;
location / {
proxy_pass http://app_backend;
}
}
Testing the Solution
Browser caches can be misleading when testing redirects. Use curl in your terminal to verify that Nginx is responding correctly to the mismatch.
1. Simulate the error condition:
curl -I http://yourdomain.com:443
Before the fix, this command returns HTTP/1.1 400 Bad Request. After applying Method 1, you should see a 301 Moved Permanently or 302 Found pointing to the https:// version of your site.
2. Validate Nginx syntax:
nginx -t
Always run this check. A single missing semicolon can take down your entire web server during a reload.
Common Docker and Network Pitfalls
- **Docker Port Mapping:** If your Docker run command uses `-p 443:80`, you are mapping external SSL traffic to an internal non-SSL port. If Nginx inside that container is expecting SSL on port 80, the mismatch is inevitable. Keep your internal and external port logic consistent.
- **HSTS Impact:** Strict Transport Security (HSTS) usually prevents browsers from making this mistake. However, it won't help with `curl`, Postman, or backend-to-backend API calls.
- **Firewall Inspection:** Some enterprise firewalls perform Deep Packet Inspection (DPI). If they strip or modify the TLS handshake, Nginx might perceive the incoming data as plain text.

