Client Sent an HTTP Request to an HTTPS Server: Causes and Fixes

If you have ever opened a page and been met with a blunt 400 Bad Request accompanied by the message *”The plain HTTP request was sent to HTTPS port”* or, in your server logs, *”client sent an HTTP request to an HTTPS server”*, you have run into one of the most common and most misunderstood TLS configuration errors on the web. The good news: it is almost always a configuration mismatch, not a broken certificate, and it is straightforward to fix once you understand what the server is actually complaining about.

This guide explains what the error means, walks through every common cause, and gives you copy-ready NGINX and Apache configuration examples to resolve it.

Key Takeaways
• The error means a plain, unencrypted HTTP request reached a port that expects encrypted HTTPS (TLS) traffic, usually port 443.
• The most frequent cause in NGINX is a `listen 443;` directive that is missing the `ssl` parameter, or an application or proxy pointing to `http://host:443`.
• You fix it by ensuring the HTTPS port is declared with `listen 443 ssl;`, redirecting plain HTTP correctly, and using `error_page 497` to convert mistaken HTTP requests into HTTPS redirects.
• Behind a load balancer or reverse proxy, the cause is usually a scheme mismatch: the proxy talks HTTP to your backend while the backend expects HTTPS. The `X-Forwarded-Proto` header is key.

What does “client sent an HTTP request to an HTTPS server” actually mean?

Every secure website speaks two different languages on two conceptually different channels. Plain HTTP sends requests as readable text. HTTPS wraps that same HTTP conversation inside a TLS (Transport Layer Security) handshake that encrypts everything first.

When a browser, script, proxy, or load balancer connects to a port that the web server has configured for HTTPS, the server expects the very first bytes it receives to be a TLS handshake. Instead, it receives a readable line such as `GET / HTTP/1.1`. The server recognizes this immediately: the client spoke plain HTTP on a channel reserved for encrypted traffic. It cannot continue the TLS negotiation, so it rejects the request with HTTP 400 and logs the now-famous line.

In short: the request arrived at the right port but in the wrong protocol. Nothing is wrong with your certificate, your domain, or your DNS. Something is sending unencrypted traffic where encrypted traffic is required.

A subtle but important detail many guides skip: NGINX is one of the few servers that tries to be *helpful* here. Rather than dropping the malformed connection, it returns a readable HTTP 400 response over the plain-text connection the client opened — which is why you can actually *see* the message in a browser. That same helpfulness is what makes `error_page 497` possible: NGINX exposes a dedicated non-standard status code (497) for “HTTP request sent to an HTTPS port,” letting you intercept the mistake and redirect the visitor to the correct `https://` URL instead of showing them an error.

What are the common causes of this error?

The error has a handful of recurring root causes. The table below maps each one to its fix so you can jump straight to the relevant section.

Cause What happens Fix
`listen 443;` without `ssl` NGINX treats port 443 as plain HTTP, so any real HTTPS (TLS) client mismatches. Change to `listen 443 ssl;`
App or proxy points to `http://host:443` A backend, script, or webhook sends plain HTTP to the TLS port. Correct the URL scheme to `https://`
`curl http://` against an HTTPS port Manual test forces plain HTTP onto port 443. Use `https://` or `curl -k` for self-signed certs
Reverse proxy scheme mismatch Proxy forwards HTTP to a backend listening for HTTPS. Align `proxy_pass` scheme; pass `X-Forwarded-Proto`
Load balancer terminates TLS, backend expects TLS LB strips TLS, then talks HTTP to an HTTPS backend. Make backend listen on HTTP, or re-encrypt to HTTPS
Mixed `listen` directives in one block Same `server` block serves both HTTP and HTTPS ports inconsistently. Separate HTTP and HTTPS into distinct server blocks

Cause 1: NGINX `listen 443` without the `ssl` parameter

This is the classic mistake. A configuration that reads:

“`nginx server { listen 443; # WRONG: no ssl parameter server_name example.com; … } “`

tells NGINX to accept plain HTTP on port 443. When a real browser connects with `https://`, it begins a TLS handshake, NGINX sees encrypted bytes it was not expecting on a plain-HTTP listener, and the connection breaks. Conversely, when the listener *does* have `ssl` but a client sends plain HTTP, you get the 400 error.

Cause 2: An application or proxy uses the wrong scheme

A microservice, webhook, cron job, or internal API call configured to reach `http://api.example.com:443/…` will send plain HTTP to the TLS port. The port number looks correct, but the scheme (`http://` vs `https://`) is wrong. This is extremely common in container and orchestration setups where service URLs are assembled from environment variables.

Cause 3: Behind a load balancer or reverse proxy

When TLS is terminated at a load balancer or CDN edge, that device decrypts the request and then forwards it to your origin server. If the load balancer forwards plain HTTP to an origin that is listening with `ssl`, the origin throws this error. The reverse can also happen: the LB tries HTTPS to a backend that only speaks HTTP. The mismatch is in the scheme used between proxy and backend, not between the visitor and the proxy.

How do you fix it in NGINX?

Start by declaring the HTTPS port correctly and giving plain-HTTP visitors a clean redirect.

1. Use a proper SSL server block:

“`nginx server { listen 443 ssl; listen [::]:443 ssl; server_name example.com www.example.com;

ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key;

error_page 497 https://$host$request_uri;

location / { root /var/www/example.com; } } “`

The `listen 443 ssl;` line is the core fix. The `error_page 497` directive is the elegant safety net described earlier: if any client mistakenly sends plain HTTP to this HTTPS port, NGINX redirects it to the correct `https://` URL instead of showing a raw 400.

2. Redirect all plain HTTP (port 80) to HTTPS:

“`nginx server { listen 80; listen [::]:80; server_name example.com www.example.com; return 301 https://$host$request_uri; } “`

Keep your HTTP (port 80) and HTTPS (port 443) listeners in separate server blocks. Mixing them in one block is a frequent source of the “mixed listen directives” version of this error.

3. Fix `proxy_pass` scheme when NGINX is the proxy:

If NGINX forwards to a backend, match the scheme to what the backend actually expects, and forward the original protocol so the application knows whether the visitor used HTTPS:

“`nginx location / { proxy_pass http://127.0.0.1:8080; # backend speaks HTTP proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; # tells backend it was HTTPS proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } “`

The `X-Forwarded-Proto` header is critical. Many application frameworks build redirect URLs and absolute links from it. If your app receives a request on plain HTTP from the proxy but `X-Forwarded-Proto` says `https`, the app correctly generates `https://` links and avoids triggering this error downstream.

How do you fix it in Apache?

Apache uses a virtual-host model. Ensure the HTTPS virtual host is bound to port 443 with SSL enabled:

“`apache ServerName example.com SSLEngine on SSLCertificateFile /etc/ssl/certs/example.com.crt SSLCertificateKeyFile /etc/ssl/private/example.com.key DocumentRoot /var/www/example.com “`

Then redirect plain HTTP to HTTPS in the port 80 virtual host:

“`apache ServerName example.com Redirect permanent / https://example.com/ “`

Behind a proxy or load balancer, instruct Apache to trust the forwarded protocol so it does not force redirects that loop or break:

“`apache SetEnvIf X-Forwarded-Proto “https” HTTPS=on “`

This is the Apache equivalent of honoring `X-Forwarded-Proto`. It lets a TLS-terminating proxy hand off plain HTTP to Apache while Apache still treats the request as secure.

How do you confirm the fix worked?

A few quick checks confirm the problem is resolved:

  • Test the HTTPS port directly: `curl -I https://example.com` should return a normal `200` or `301`, not a 400.
  • Reproduce the original error on purpose: `curl -I http://example.com:443` should now return a clean redirect (thanks to `error_page 497`) rather than the raw error message.
  • Validate the config before reloading: run `nginx -t` or `apachectl configtest`, then reload. Never reload a config that fails its syntax test.
  • Watch the logs: tail your error log while testing. The “client sent an HTTP request to an HTTPS server” line should stop appearing.

If the error persists only for certain requests, the culprit is almost always an internal client — a webhook, health check, or service-to-service call still using `http://` against the TLS port.

Skip the configuration headaches with DarazHost

Diagnosing TLS mismatches is a lot easier when your hosting platform ships HTTPS configured correctly out of the box. With DarazHost web hosting, every account includes free SSL certificates and automatically configured HTTPS through cPanel AutoSSL — your `listen 443 ssl;` equivalents and HTTP-to-HTTPS redirects are set up for you, so the “client sent an HTTP request to an HTTPS server” error rarely appears in the first place.

For teams that manage their own stack, DarazHost VPS hosting gives you full root access to fine-tune NGINX or Apache SSL configurations, set custom `proxy_pass` schemes, and control `X-Forwarded-Proto` behavior behind your own load balancers. And whenever an SSL or HTTPS issue does surface, our 24/7 expert support is on hand to help you trace the scheme mismatch and get encrypted traffic flowing again.

·

Frequently asked questions

Is “client sent an HTTP request to an HTTPS server” a certificate problem?

No. The error appears before any certificate is validated. It means a plain HTTP request reached a TLS port, so the TLS handshake never started. Your certificate can be perfectly valid and you will still see this error if a client uses the wrong scheme.

What is HTTP status code 497 in NGINX?

497 is a non-standard status code NGINX uses internally to represent “an HTTP request was sent to an HTTPS port.” You can intercept it with `error_page 497 https://$host$request_uri;` to redirect those mistaken requests to the correct HTTPS URL instead of returning a 400 error.

Why does the error only happen behind my load balancer?

Because the scheme between the load balancer and your origin is mismatched. The visitor reaches the load balancer over HTTPS, but the load balancer forwards plain HTTP to an origin that is listening with `ssl`. Either configure the origin to accept HTTP on a separate port or re-encrypt traffic to HTTPS between the LB and origin.

Can I just disable HTTPS to make the error go away?

You should not. Disabling HTTPS removes encryption and breaks trust, search ranking, and modern browser features. The correct fix is to align the protocol — make sure clients use `https://` and that your listeners and proxies agree on the scheme.

How do I tell which client is sending plain HTTP?

Check your web server’s access and error logs for the offending request’s IP address and user agent. Internal services, monitoring health checks, and webhooks are the usual suspects when the error appears intermittently rather than for all visitors.

About the Author

Leave a Reply