Nginx. How do I reject request to unlisted ssl virtual server?

Solution 1:

The answer by cjc already correctly pointed out the problem with trying to match host names when SSL is enabled. However, it is possible to do it, like this:

server {
    ...

    if ($host !~* ^validname\.domain\.com$ ) {
        return 444;
    }
    ...
}

Note: yes it is true that generally if is evil, but it is safe to use if in this case. (Read the linked page if you need to convince yourself.)

Contrarily to what has been suggested, just adding the following block won't work:

server {
    listen 80;
    listen 443 ssl;
    return 444;
}

because an SSL certificate that matches validname.domain.com won't match some random domain name. I've tried it, and nginx acted like the block was not present at all.

This also won't work:

server {
    listen       443;
    server_name    _;
    return 444; 
}

because it will make every single HTTPS connection on port 443 fail, even those that should go through. I've tried this one too. wget reported an SSL handshake error.

Solution 2:

It doesn't work that way: the SSL handshake happens before HTTP, so the name on the certificate will get evaluated in the browser before you can redirect or do anything else inside the nginx configuration.


Solution 3:

Most answers here are about why it doesn't work, not how to make it work.

Here is how - you need to make such catch-all server a 'default_server' and need to provide paths to cert/key so that it can decrypt incoming ssl request and match the Host header:

server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate <path to cert>;
    ssl_certificate_key <path to key>;
    return 404;
}

Note the ssl_certificate/ssl_certificate_key there. If they are not specified, nginx still tries to use such default_server and fails as it can't accept ssl connection w/o a cert/key. One can use any cert/key e.g. self-signed. ...

To generate a self-signed certificate:

openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 

Also see https://serverfault.com/a/841643/87439


Solution 4:

At this time, the answer to this question can be updated.

For Nginx ≥ 1.19.4: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_reject_handshake

server {
    listen               443 ssl;
    ssl_reject_handshake on;
}

server {
    listen              443 ssl;
    server_name         example.com;
    ssl_certificate     example.com.crt;
    ssl_certificate_key example.com.key;
}

For Nginx < 1.19.4: Using https://git.hakase.app/Hakase/openssl-patch

http {
    # control options
    strict_sni on;
    strict_sni_header on;

    # fake server block
    server {
        server_name  localhost;
        listen       80;
        listen       443 ssl default_server; # "default_server" is necessary
        ssl_certificate /root/cert.crt; # Can be any certificate here
        ssl_certificate_key /root/cert.key; # Can be any certificate here

        location / {
            return 444;
        }
    }

    # normal server blocks
    server {
        server_name  normal_domain.tld;
        listen       80;
        listen       443 ssl;
        ssl_certificate /root/cert.crt; # Your real certificate here
        ssl_certificate_key /root/cert/cert.key; # Your real certificate here

        location / {
            echo "Hello World!";   
        }
    }
}

For newbies who is not familiar with applying the patch to Nginx, you can check this: https://blog.sion.moe/methods-to-prevent-leaking-websites-origin-server-ip-behind-cdn/