Servers and proxies

Nginx: no "ssl_certificate" is defined in server listening on SSL port

By · Last verified

Error messageno "ssl_certificate" is defined in server listening on SSL port while SSL handshaking, client: 203.0.113.10, server: 0.0.0.0:443

This error means a TLS client reached a server block on an SSL port that has no ssl_certificate, so Nginx had nothing to present and aborted the handshake. Most often that block is the default server for port 443, which receives connections by IP address, without SNI, or for hostnames you do not serve.

Quick fix

The port 443 server block without ssl_certificate needs a certificate or ssl_reject_handshake on;.

List every server block on port 443 and see which ones lack a certificate:

sudo nginx -T 2>/dev/null | grep -nE "server_name|listen .*443|ssl_certificate |default_server"

Then either give that block a certificate, or make your catch-all reject TLS handshakes explicitly (Nginx 1.19.4 or later):

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_reject_handshake on;
}

Run sudo nginx -t && sudo systemctl reload nginx. The SSL checker shows which certificate each hostname now receives.

Causes

1. A catch-all or default server without a certificate

When any server uses listen 443 ssl, every server on that address and port handles TLS. A catch-all such as server_name _; copied from a port 80 template, or the distribution's default site, often has no certificate.

  • Confirm: the error's server: field is 0.0.0.0:443 or [::]:443, and nginx -T shows a block on that port without ssl_certificate.
  • Fix: add ssl_reject_handshake on; to that block, or remove it if you do not need it.

2. Clients that do not send SNI, or connect by IP

Scanners, health checks and old clients connect without SNI. Nginx cannot pick the named server and falls back to the default.

  • Confirm: openssl s_client -connect 203.0.113.10:443 -noservername fails, while -servername example.com works.
  • Fix: mark the real site as default_server if it should answer IP connections, or keep the rejecting catch-all. Make health checks send the hostname.

3. Certificate directives in the wrong place

ssl_certificate belongs in http or server context. It is not inherited from another server block, and a location block cannot hold it.

  • Confirm: the certificate is configured only in a different server block for the same name, or in an include file that is not loaded (nginx -T does not show it).
  • Fix: move the directives into the server block that has the matching server_name, or include the snippet there.

4. Duplicate server blocks for the same name

Tools such as Certbot's Nginx plugin edit one block; if you have two blocks with the same server_name on 443, Nginx uses the first and warns about a conflicting name.

  • Confirm: nginx -t prints conflicting server name "example.com" on 0.0.0.0:443, ignored.
  • Fix: merge the blocks so one block per name and port holds the certificate.

5. Certificate loaded from variables

Since Nginx 1.15.9 ssl_certificate can contain variables. If a variable resolves to an empty value or missing file, the handshake fails for those names.

  • Fix: check the map that sets the variable and provide a default.

Fixes by environment

On every platform, give the default server on port 443 a certificate or ssl_reject_handshake on;.

Debian / Ubuntu packages: the default site in /etc/nginx/sites-enabled/ listens on port 80 only by default, but edited copies often add 443. Disable it or give it ssl_reject_handshake on;.

Docker / Nginx Proxy Manager: the generated default server may have no certificate. Configure a default certificate or a rejecting default in the proxy settings.

Kubernetes ingress-nginx: the controller serves its own fake default certificate for unknown hosts, so this message rarely appears; a name mismatch for unknown hosts is expected there.

Older Nginx (before 1.19.4): ssl_reject_handshake is not available; give the catch-all a self-signed certificate and return 444 to close connections.

Don't do this

  • Serve your real certificate on the catch-all for every unknown name. Visitors reaching the wrong name get a name mismatch, and you advertise your domains to IP scanners.
  • Remove ssl from listen to make the error disappear. The port would then speak plain HTTP to TLS clients and every HTTPS visitor would fail.
  • Ignore the log entirely. Usually harmless, but the same message also appears when a real site loses its certificate during a config change.

FAQ

Why does my site work while the log keeps showing this error?

Your named server has a certificate, but scanners and bots connect by IP or with unknown hostnames and land on the default server, which has none. The error is noise for those clients but harmless for your visitors. Add a catch-all with ssl_reject_handshake on; to stop it.

Can I put ssl_certificate in the http block?

Yes. Directives in the http block are inherited by every server that does not set its own. This gives every SSL server a fallback certificate, but clients that reach the wrong server will see a name mismatch instead of a handshake failure.

Is this the same as the "no ssl_certificate is defined for the listen ... ssl directive" error?

They have the same root cause. The configuration-time variant is reported by nginx -t when a server block with listen ... ssl has no certificate; the handshake-time variant appears in error.log when a client reaches such a block.

Sources

  1. nginx ngx_http_ssl_module (ssl_certificate, ssl_reject_handshake)
  2. nginx: Configuring HTTPS servers