45

Can nginx location blocks match a URL query string?

For example, what location block might match HTTP GET request

GET /git/sample-repository/info/refs?service=git-receive-pack HTTP/1.1
11

3 Answers 3

59

Can nginx location blocks match a URL query string?

Short answer: No.

Long answer: There is a workaround if we have only a handful of such location blocks.

Here's a sample workaround for 3 location blocks that need to match specific query strings:

server {
  #... common definitions such as server, root
    
  location / {
    error_page 418 = @queryone;
    error_page 419 = @querytwo;
    error_page 420 = @querythree;

    if ( $query_string = "service=git-receive-pack" ) { return 418; }
    if ( $args ~ "service=git-upload-pack" ) { return 419; }
    if ( $arg_somerandomfield = "somerandomvaluetomatch" ) { return 420; }

    # do the remaining stuff
    # ex: try_files $uri =404;
    
  }

  location @queryone {
    # do stuff when queryone matches
  }

  location @querytwo {
    # do stuff when querytwo matches
  }

  location @querythree {
    # do stuff when querythree matches
  }
}

You may use $query_string, $args or $arg_fieldname. All will do the job. You may know more about error_page in the official docs.

Warning: Please be sure not to use the standard HTTP codes.

16
  • 1
    Interesting approach! May I recommend $args ~ "service=git-send-pack" instead of $args = "service=git-send-pack"? This form accommodates multiple query parameters. Oct 28, 2016 at 21:52
  • 1
    stackoverflow.com/a/40313590/107158 illustrates the approach that I followed to handle query string arguments. Like your answer, mine uses if and $arg_fieldname, but uses rewrite instead of error_page and location @name. Note that in that example, my attempts at using @name for the replacement parameter in rewrite were unsuccessful. Oct 28, 2016 at 21:57
  • 1
    By the way, it should be $args ~ and $arg_somerandomfield =. Oct 28, 2016 at 22:02
  • 2
    One can also use nginx map feature for this purpose, which is faster. Oct 29, 2016 at 10:24
  • 1
    @PothiKalimuthu, thanks for clarifying this. What I have done in the meanwhile is to replace the query parameter by a url path one like this feedback/{auth_key} instead of /feedback?auth_key=abc. This way I don't need to use if, I can define location pattern using regex and that's it.
    – W.M.
    Sep 11, 2017 at 16:26
9

I know this question is over a year old, but I've spent the last few days destroying my brain over a similar problem. I wanted different authentication and handling rules for public and private repos, including pushing and pulling. This is what I finally came up with, so I figured I'd share. I know if is a tricky directive, but this seems to work for me just fine:

# pattern for all repos, public or private, followed by username and reponame
location ~ ^(?:\/(private))?\/([A-Za-z0-9]+)\/([A-Za-z0-9]+)\.git(\/.*)?$ {

    # if this is a pull request
    if ( $arg_service = "git-upload-pack" ) {

        # rewrite url with a prefix
        rewrite ^ /upload$uri;

    }

    # if this is a push request
    if ( $arg_service = "git-receive-pack" ) {

        # rewrite url with a prefix
        rewrite ^ /receive$uri;

    }

}

# for pulling public repos
location ~ ^\/upload(\/([A-Za-z0-9]+)\/([A-Za-z0-9]+)\.git(\/.*)?)$ {

    # auth_basic "git";
    # ^ if you want

    # ...
    # fastcgi_pass unix:/var/run/fcgiwrap.socket;
    # ...

}

# for pushing public repos
location ~ ^\/receive(\/([A-Za-z0-9]+)\/([A-Za-z0-9]+)\.git(\/.*)?)$ {

    # auth_basic "git";
    # ^ if you want

    # ...
    # fastcgi_pass unix:/var/run/fcgiwrap.socket;
    # ...

}

# for pulling private repos
location ~ ^\/upload\/private(\/([A-Za-z0-9]+)\/([A-Za-z0-9]+)\.git(\/.*)?)$ {

    # auth_basic "git";
    # ^ if you want

    # ...
    # fastcgi_pass unix:/var/run/fcgiwrap.socket;
    # ...

}

# for pushing private repos
location ~ ^\/receive\/private(\/([A-Za-z0-9]+)\/([A-Za-z0-9]+)\.git(\/.*)?)$ {

    # auth_basic "git";
    # ^ if you want

    # ...
    # fastcgi_pass unix:/var/run/fcgiwrap.socket;
    # ...

}
1
  • 1
    As NGINX uses PCRE library, I would suggest to use \w instead of [A-Za-z0-9_] to diminish verbosity.
    – Stphane
    Nov 29, 2020 at 22:41
-1

There is another way this can be done, if you are using nginx as a proxy.

set a variable in the server block:

set $nocache="0";

Inside the location block, add the if:

if ( $arg_<query string to match> = "<query string value>") { set $nocache "1"; }

And add two new proxy directives:

proxy_cache_bypass $nocache ;
proxy_no_cache $nocache ;

it will always forward to the upstream server with no cache

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.