Nginx – Why won’t Nginx return a 404 error for URLs starting with a question mark

301-redirecthttpsnginx

I'm trying to get a rewrite rule working and I've discovered that if the URL begins with a question mark, Nginx fails to return 404 error as expected. Instead, index.html as defined in the root directive is served for all urls beginning with "?". (e.g. example.com/?page-does-not-exist works for the home page but should not)

This is making it impossible to get a redirect from example.com/?format=feed&type=rss -> example.com/rss.xml working. This is for a blog migrated from Joomla to Jekyll.

Put simply, visiting example.com/123 returns a 404, and visiting example.com/?123 does not. The latter returns the site's home page at the URL example.com?/123. (There is no file or folder '123', so requests for it should always fail.)

Everything works perfectly with the site, including the HTTPS redirect, except for the expected 404 redirects not happening for URLs starting with a question mark. How do I fix this?

Here's my configuration:

server {
    server_name example.com;
    root /var/www/example.com;
    index index.html;
    listen 443;
    ssl on;      
    ...

    rewrite "/?format=feed&type=rss" https://example.com/rss.xml permanent;

    location ~* \.(?:ico|css|js|gif|jpeg|jpg|png|txt|svg|eot|woff|ttf)$ {
            expires max;
            add_header Pragma public;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
            add_header Access-Control-Allow-Origin *;
            valid_referers none blocked example.com;
            if ($invalid_referer) {
                    return 403;
            }
    }
}

# HTTP --> HTTPS
server {
        listen 80;
        server_name www.example.com example.com;
        return 301 https://example.com$request_uri;
}

Best Answer

The question mark signifies the end of the path portion of the uri and the beginning of the query string. Hence why nginx will not return 404 based on the portion after the question mark; the requested resource does exist and the query string parameters are just passed to it.

Regarding the other part of the question, I believe that nginx's rewrite statement does not actually match against the query string portion of the uri.

You should however be able to do something along the lines of:

if ($args ~ "format=feed&type=rss") {
    rewrite ^ https://example.com/rss.xml? permanent;
}