Score:0

NGINX reverse proxy "if not" statements syntax?

us flag

I have an nginx reverse proxy that I want to basically redirect any url request that isn't my specific top level domain to 404.

So far I think I want something like this at the bottom of my /etc/nginx/sites-enabled/site.conf:

server {
        listen 80;
        if ($host != *domain.com) {
                return 404 http://$host$request_uri;
        }
}

but I don't know the exact syntax here. I already have other redirect blocks above this one, I just want any url requests for IP-only hostnames or other domains to immediately get a 404 and not get passed to backend servers.

Additionally, I also have TLS passthrough I'd like to do the same thing on, but I am completely lost on how to implement an if statement on the stream block. My stream block looks like this in /etc/nginx/nginx.conf:

stream {
        map $ssl_preread_server_name $name {
                server.domain.com           server;
                www.domain.com              www;
        }
        upstream server {
                server backendip:443;
        }
        upstream www {
                server backendip2:443;
        }
        server {
                listen 443;
                proxy_pass $name;
                ssl_preread on;
        }
}

For that, would I implement something like this right below the first server block?

        server {
                listen 443;
                if ($ssl_preread_server_name != *domain.com) {
                        return 404 https://$host$request_uri;
                }
        }

I think for security I should also be including default_server in the listen line for both of these blocks with if statements, or does it matter?

I'm just assuming a lot of syntax here, so any help would be much appreciated.

Ivan Shatsky avatar
gr flag
You can either check exact matching/non matching on strings or perform case sensitive/insensitive matching against a regex pattern. Looks like you are looking for `if ($var !~ (.*\.)?domain\.com$) { ... }`. Recently I gave a detailed answer on blocking all access by the IP address rather that the hostname [here](https://stackoverflow.com/questions/69824838/block-access-with-https-ipaddress-on-nginx/69825652#69825652).
ehammer avatar
us flag
I checked out your other post, that helps for my http blocks. Still unsure of how to do it with nginx TLS passthrough (return 444 if SNI is not a certain domain)
Ivan Shatsky avatar
gr flag
Well, i'm not as knowledgeable about stream module as I am about http one. You can't use anything from the rewrite module (including `if`). You can't use `return 444` since stream module has it's own [`return`](http://nginx.org/en/docs/stream/ngx_stream_return_module.html) directive different from the http one.
Score:1
gr flag

I didn't know how to gracefully close a connection with a stream module, so I'm not sure if it would be a correct solution, but it worth to try (you can use any free port for the dummy server/upstream):

stream {
    map $ssl_preread_server_name $name {
        server.domain.com           server;
        www.domain.com              www;
        default                     dummy;
    }
    upstream server {
        server backendip:443;
    }
    upstream www {
        server backendip2:443;
    }
    upstream dummy {
        backend 127.0.0.1:4343;
    }
    server {
        listen 443;
        proxy_pass $name;
        ssl_preread on;
    }
    server {
        listen 4343;
        return "";
    }
}

Update

After giving the above solution a test, I can confirm it is workable. However it produces an error log entry like

WSARecv() failed (10053: An established connection was aborted by the software in your host machine) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:443, upstream: "127.0.0.1:4343", bytes from/to client:517/0, bytes from/to upstream:0/517

So after some thinking I tried this one:

http {
    server {
        listen              4343 ssl;
        ssl_certificate     /path/to/selfsigned.crt;
        ssl_certificate_key /path/to/selfsigned.key;
        return              444;
    }
}
stream {
    map $ssl_preread_server_name $name {
        server.domain.com           server;
        www.domain.com              www;
        default                     dummy;
    }
    upstream server {
        server backendip:443;
    }
    upstream www {
        server backendip2:443;
    }
    upstream dummy {
        backend 127.0.0.1:4343;
    }
    server {
        listen 443;
        proxy_pass $name;
        ssl_preread on;
    }
}

Self-signed certificate and key can be created using the following command:

openssl req -nodes -new -x509 -subj "/CN=localhost" -keyout /path/to/selfsigned.key -out /path/to/selfsigned.crt

This one closes the connection correctly.

Moreover, even this worked:

http {
    server {
        listen              80 default_server;
        return              444;
    }
}
stream {
    map $ssl_preread_server_name $name {
        server.domain.com           server;
        www.domain.com              www;
        default                     dummy;
    }
    upstream server {
        server backendip:443;
    }
    upstream www {
        server backendip2:443;
    }
    upstream dummy {
        backend 127.0.0.1:80;
    }
    server {
        listen 443;
        proxy_pass $name;
        ssl_preread on;
    }
}

Of course, curl complains on handshake attempt for the incorrect SSL version number and there are funny entries in the access log like

127.0.0.1 - - [05/Nov/2021:01:04:34 +0200] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03L\xF9[\xB2\x7F\x99\xB1(\xAC\xB4\x91}\xE2N\xC6H\xE3\xB3_\xD1\xEA\xA3C\x1D\xE5\xE5\xB6\x02\xDB\x04h\xB6 *\xCB\x0E\xC0\xF5\xB7\xAF[y|\x1B\x14_\xC2g\xEA\xA2\x1E\xB4\xC4Bj3t\xE8d\xE72vm\xF2\x1B\x00>\x13\x02\x13\x03\x13\x01\xC0,\xC00\x00\x9F\xCC\xA9\xCC\xA8\xCC\xAA\xC0+\xC0/\x00\x9E\xC0$\xC0(\x00k\xC0#\xC0'\x00g\xC0" 400 163 "-" "-"

but error log is clear.

ehammer avatar
us flag
That seems to work. I never really wanted to close a connection gracefully. I wanted more of the http444 type of functionality. Silently failing.
Ivan Shatsky avatar
gr flag
@ehammer Check the update.
ehammer avatar
us flag
I like it. I already had a HTTP default block pointing 127.0.0.1:80 to HTTP444, so this integrates very nicely with that. Does the ```default``` option under the map block actually mean default_server or is this just a placeholder name?
Ivan Shatsky avatar
gr flag
It is a special keyword for the map block meaning this value will be returned if no other variants matched. If it isn't specified explicitly, default value is an empty string.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.