DevOps

Nginx redirect HTTP to HTTPS and avoid 400 error

How to configure Nginx to redirect HTTP to HTTPS (nginx redirect to https, nginx ssl configuration). Use separate listen sockets or error_page 497 for same-port cases.

1 answer 1 view

How to configure Nginx to only listen on HTTPS and not respond to HTTP requests? I’m trying to set up a redirect from HTTP to HTTPS or return a 444 status code, but instead I’m getting the Nginx stub page with ‘400 Bad Request: The plain HTTP request was sent to HTTPS port’.

Here’s my current configuration:

server {
listen 12345;
server_name 1.1.1.1;

return 301 https://$host:12345$request_uri;

}

server {
listen 12345ssl;
server_name 1.1.1.1;

ssl_certificate     /etc/nginx/mtls/server.crt;
ssl_certificate_key /etc/nginx/mtls/server.key;

ssl_client_certificate /etc/nginx/mtls/ca.crt;
ssl_verify_client optional;

if ($ssl_client_verify != SUCCESS) {
    return 444;
}

location / {
    proxy_pass http://127.0.0.1:1010;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
}

}

When configuring Nginx to only accept HTTPS connections, the “400 Bad Request: The plain HTTP request was sent to HTTPS port” error occurs when a plain HTTP request reaches a socket expecting TLS. To properly configure nginx redirect to HTTPS while avoiding this error, you should use separate listen directives for HTTP and HTTPS traffic, ensuring that your HTTP server doesn’t have ssl enabled while your HTTPS server does.

Contents

Understanding the Error

The error message “The plain HTTP request was sent to HTTPS port” occurs when a client sends a plain HTTP request to a socket configured to expect TLS-encrypted traffic. This happens because the Nginx security model requires strict separation between HTTP and HTTPS sockets.

In your current configuration, you’ve set up both server blocks to listen on the same port (12345) but with different SSL settings. The first server block listens on port 12345 without SSL, while the second listens on 12345 with SSL. When HTTP requests reach the SSL-enabled socket, Nginx responds with the 400 error because it expects a TLS handshake but receives plain HTTP text instead.

The root cause is that SSL termination requires a dedicated socket that expects encrypted traffic from the start. Mixing plain HTTP and HTTPS on the same port without proper handling leads to protocol mismatches and this error message.

Correct Configuration with Separate Ports

The most reliable approach is to use separate ports for HTTP and HTTPS traffic. This follows standard network practices and eliminates the protocol confusion that causes the 400 error.

For a proper nginx redirect to HTTPS setup, create two distinct server blocks:

nginx
# HTTP server - redirects all traffic to HTTPS
server {
    listen 80;
    server_name 1.1.1.1;
    
    return 301 https://$host$request_uri;
}

# HTTPS server - handles encrypted traffic
server {
    listen 443 ssl;
    server_name 1.1.1.1;
    
    ssl_certificate /etc/nginx/mtls/server.crt;
    ssl_certificate_key /etc/nginx/mtls/server.key;
    
    ssl_client_certificate /etc/nginx/mtls/ca.crt;
    ssl_verify_client optional;
    
    if ($ssl_client_verify != SUCCESS) {
        return 444;
    }
    
    location / {
        proxy_pass http://127.0.0.1:1010;
        
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
    }
}

Key points about this nginx ssl configuration:

  • The HTTP server uses listen 80 without any ssl parameters
  • The HTTPS server uses listen 443 ssl which clearly indicates it expects encrypted traffic
  • The redirect uses a 301 status code for permanent redirection
  • Client certificate verification remains intact for the HTTPS server

Redirect Configuration Example

Here’s a more complete example showing how to implement nginx redirect http to HTTPS with proper handling:

nginx
# HTTP server block - redirects to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    
    # Include any additional server names if needed
    # server_name alternate.domain.com;
    
    return 301 https://$host$request_uri;
}

# HTTPS server block with SSL configuration
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;
    
    # SSL configuration
    ssl_certificate /path/to/your/fullchain.pem;
    ssl_certificate_key /path/to/your/privkey.pem;
    
    # Security enhancements
    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;
    
    # HSTS header (optional but recommended)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Location blocks
    location / {
        try_files $uri $uri/ =404;
    }
    
    # Additional location blocks as needed
}

This approach ensures that:

  • All HTTP traffic is properly redirected to HTTPS
  • The HTTPS server only receives encrypted requests
  • Your mTLS configuration works correctly without protocol conflicts

Same-Port Workaround Using error_page

If you absolutely must use the same port for both HTTP and HTTPS traffic (which is not recommended), you can use the error_page 497 workaround. This method catches the 497 status code that Nginx returns when a plain HTTP request is sent to an SSL-enabled socket:

nginx
server {
    listen 12345 ssl;
    server_name 1.1.1.1;
    
    ssl_certificate /etc/nginx/mtls/server.crt;
    ssl_certificate_key /etc/nginx/mtls/server.key;
    
    ssl_client_certificate /etc/nginx/mtls/ca.crt;
    ssl_verify_client optional;
    
    # Catch plain HTTP requests on SSL port and redirect
    error_page 497 https://$host:12345$request_uri;
    
    if ($ssl_client_verify != SUCCESS) {
        return 444;
    }
    
    location / {
        proxy_pass http://127.0.0.1:1010;
        
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
    }
}

According to community experts, this technique uses Nginx’s built-in handling of protocol mismatches to redirect requests to the correct scheme.

However, be aware that this approach has limitations:

  • It’s more complex to maintain
  • It may not work properly in all proxy/load balancer scenarios
  • It’s less efficient than using separate ports

Testing Your Configuration

After making changes to your nginx ssl configuration, test it thoroughly to ensure it works as expected:

bash
# Test Nginx configuration syntax
sudo nginx -t

# Test HTTP to HTTPS redirect
curl -I http://1.1.1.1:12345/

# Test HTTPS connection with valid client certificate
curl -I --cert /path/to/client.crt --key /path/to/client.key https://1.1.1.1:12345/

# Test HTTPS connection without client certificate (should return 444)
curl -I https://1.1.1.1:12345/

The official Nginx documentation recommends testing your configuration changes to ensure they behave as expected before deploying them to production.

Advanced mTLS Considerations

When using mutual TLS (mTLS) as in your configuration, there are additional considerations:

  1. Certificate Validation: Ensure your CA certificate is properly configured and accessible to Nginx. Invalid certificates can lead to unexpected behavior.

  2. Client Certificate Errors: Consider how to handle cases where clients present invalid or expired certificates. Your current configuration returns 444, which effectively closes the connection.

  3. Protocol Compatibility: Some older clients may not support SNI (Server Name Indication), which can cause issues with SSL handshakes. Make sure your configuration accounts for this.

  4. Load Balancer Integration: If you’re using a load balancer or reverse proxy in front of Nginx, ensure it’s properly configured to pass client certificates and preserve the original protocol information.

According to troubleshooting guides, issues with mTLS often stem from improper SSL handshakes or certificate validation problems rather than the redirect mechanism itself.

Sources

Conclusion

To properly configure nginx redirect to HTTPS and avoid the “plain HTTP request was sent to HTTPS port” error, always use separate listen directives for HTTP and HTTPS traffic. The most reliable approach is to configure a dedicated HTTP server on port 80 that redirects to HTTPS, and a separate HTTPS server on port 443 with your ssl_certificate and ssl_verify_client settings. This separation eliminates protocol confusion and ensures your nginx ssl configuration works as intended. While same-port workarounds exist, they’re less reliable and more complex to maintain than the standard port separation method.

Authors
Verified by moderation
Moderation
Nginx redirect HTTP to HTTPS and avoid 400 error