nginx/nginx · error

400 The plain HTTP request was sent to HTTPS port

Error message

400 The plain HTTP request was sent to HTTPS port

What it means

This is nginx's built-in error page for pseudo-status 497 (NGX_HTTP_TO_HTTPS, defined in src/http/ngx_http_request.h), served to the client with status 400. When a listener has 'ssl' enabled, nginx expects a TLS handshake; if the first bytes parse as a plain HTTP request line instead, nginx logs "client sent plain HTTP request to HTTPS port" (src/http/ngx_http_request.c:2142) and sends this page from the ngx_http_error_pages[] table in ngx_http_special_response.c. It means the client used the wrong scheme for that port.

Source

Thrown at src/http/ngx_http_special_response.c:282

"<head><title>400 The SSL certificate error</title></head>"
CRLF
"<body>" CRLF
"<center><h1>400 Bad Request</h1></center>" CRLF
"<center>The SSL certificate error</center>" CRLF
;


static char ngx_http_error_496_page[] =
"<html>" CRLF
"<head><title>400 No required SSL certificate was sent</title></head>"
CRLF
"<body>" CRLF
"<center><h1>400 Bad Request</h1></center>" CRLF
"<center>No required SSL certificate was sent</center>" CRLF
;


static char ngx_http_error_497_page[] =
"<html>" CRLF
"<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>"
CRLF
"<body>" CRLF
"<center><h1>400 Bad Request</h1></center>" CRLF
"<center>The plain HTTP request was sent to HTTPS port</center>" CRLF
;


static char ngx_http_error_500_page[] =
"<html>" CRLF
"<head><title>500 Internal Server Error</title></head>" CRLF
"<body>" CRLF
"<center><h1>500 Internal Server Error</h1></center>" CRLF
;


static char ngx_http_error_501_page[] =

View on GitHub (pinned to 3f6f7824d4)

Solutions

  1. Change the client to use https:// against the TLS port (or http:// against a plain port).
  2. If you want an automatic upgrade, add 'error_page 497 =301 https://$host$request_uri;' to the ssl server block.
  3. Split listeners: one 'listen 80;' server that redirects and one 'listen 443 ssl;' server for TLS traffic.
  4. For proxy hops, fix the upstream scheme: 'proxy_pass https://backend;' or set the load balancer backend protocol to HTTPS/TLS.

Example fix

// before
server {
    listen 443 ssl;
    server_name example.com;
}
// client: curl http://example.com:443/  -> 400 The plain HTTP request was sent to HTTPS port

// after
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    server_name example.com;
    error_page 497 =301 https://$host$request_uri;
}
Defensive patterns

Strategy: validation

Validate before calling

# before connecting, verify the scheme/port pairing actually served
scheme=443_tls_only
curl -sI "http://$host:$port/" | head -n1   # if this prints '400 ... HTTPS port', use https
curl -sI "https://$host:$port/" | head -n1  # this must succeed

Try / catch

try:
    r = requests.get(f"https://{host}:{port}/", timeout=10)
except requests.exceptions.SSLError:
    # port is NOT speaking TLS even though we assumed https
    raise

Prevention

When it happens

Trigger: Running 'curl http://example.com:443/' against a 'listen 443 ssl;' server; a health checker or uptime monitor configured with http:// against the TLS port; a load balancer whose backend protocol is HTTP while the target expects TLS; an internal proxy_pass that omits 'https://' so nginx speaks plain HTTP to a TLS upstream.

Common situations: Container port remapping that maps container 443 to host 8080 and clients then try http://host:8080; AWS ELB/ALB target-group protocol mismatch; scripts where the scheme is built from config and defaults to http; migration from port 80 to 443 without updating monitoring probes.

Related errors


AI-assisted analysis of nginx/nginx@3f6f7824d4 (2026-08-22). Data as JSON: /api/errors/e99c4e2a8a97f1ab. Report an issue: GitHub.