Nginx Location Block: How to Match URL Query Strings for Precise Routing


2 views

When working with Nginx configuration, developers often need to route requests based on query parameters. A common use case is handling Git HTTP requests like:

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

Standard location blocks only match against the URI path, not the query string. This limitation requires specific techniques for query parameter-based routing.

Nginx provides the $args and $query_string variables for accessing query parameters. While you can't directly match query strings in location blocks, you can use conditional logic:

location /git/ {
    if ($args ~* "service=git-receive-pack") {
        # Handle Git receive pack requests
        auth_basic "Git Receive Pack";
        auth_basic_user_file /etc/nginx/git.htpasswd;
    }
    
    if ($args ~* "service=git-upload-pack") {
        # Handle Git upload pack requests
        auth_basic "Git Upload Pack";
        auth_basic_user_file /etc/nginx/git.htpasswd;
    }
}

For complex routing scenarios, the map directive offers more maintainable query string handling:

map $args $git_service {
    default         "";
    "~*service=git-receive-pack"    "receive";
    "~*service=git-upload-pack"     "upload";
}

server {
    location /git/ {
        if ($git_service = "receive") {
            # Receive pack configuration
        }
        
        if ($git_service = "upload") {
            # Upload pack configuration
        }
    }
}

Here's a complete example for handling Git smart HTTP protocol requests:

server {
    listen 80;
    server_name git.example.com;
    
    location ~ ^/git/.*/info/refs$ {
        if ($args ~* "service=git-upload-pack") {
            # Read-only access
            auth_basic "Git Read Access";
            auth_basic_user_file /etc/nginx/git-read.htpasswd;
            include fastcgi_params;
            fastcgi_pass unix:/var/run/fcgiwrap.socket;
        }
        
        if ($args ~* "service=git-receive-pack") {
            # Write access
            auth_basic "Git Write Access";
            auth_basic_user_file /etc/nginx/git-write.htpasswd;
            include fastcgi_params;
            fastcgi_pass unix:/var/run/fcgiwrap.socket;
        }
    }
    
    location ~ ^/git/.*/git-receive-pack$ {
        auth_basic "Git Write Access";
        auth_basic_user_file /etc/nginx/git-write.htpasswd;
        include fastcgi_params;
        fastcgi_pass unix:/var/run/fcgiwrap.socket;
    }
}

When implementing query string matching:

  • Avoid excessive if blocks as they're not nginx's preferred conditional
  • Consider using map for complex matching patterns
  • Regular expressions in query string matching can impact performance
  • Cache static resources before they reach query string matching logic

For scenarios requiring more sophisticated query parameter handling:

  • Use Lua scripting with OpenResty for advanced routing logic
  • Consider API gateway solutions like Kong when query parameters drive complex routing
  • Implement a small proxy service for cases requiring extensive query string processing

Nginx location blocks primarily match against the URI part of a request, not including the query string (everything after the ?). This is a common source of confusion for developers working with REST APIs or dynamic content.

Consider this Git HTTP request:

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

A standard location block like:

location /git/ {
    # Configuration
}

will match the URI, but won't consider the service=git-receive-pack query parameter.

1. Using the $args Variable

Nginx provides the $args variable containing the full query string:

location /git/ {
    if ($args ~* "service=git-receive-pack") {
        # Specific configuration for git-receive-pack
    }
    # Default configuration
}

2. Separate Location Blocks for Different Services

For Git's smart HTTP protocol:

location ~ ^/git/.+/info/refs$ {
    if ($arg_service = "git-upload-pack") {
        # Pull operations
        proxy_pass http://git-upload-pack-backend;
    }
    if ($arg_service = "git-receive-pack") {
        # Push operations
        proxy_pass http://git-receive-pack-backend;
    }
}

3. Using map Directive for Complex Matching

For more complex scenarios:

map $args $git_service {
    default        "";
    "~service=git-upload-pack"   "upload";
    "~service=git-receive-pack"  "receive";
}

server {
    location /git/ {
        if ($git_service = "upload") {
            # Upload pack configuration
        }
        if ($git_service = "receive") {
            # Receive pack configuration
        }
    }
}

When working with query strings in Nginx:

  • Avoid excessive use of if directives as they have performance implications
  • Remember that $args contains the raw query string while $query_string is an alias
  • For security-sensitive operations, always validate and sanitize query parameters

Here's how you might handle API versioning via query parameters:

location /api/ {
    if ($arg_v = "1.0") {
        proxy_pass http://api-v1;
    }
    if ($arg_v = "2.0") {
        proxy_pass http://api-v2;
    }
    # Default to latest version
    proxy_pass http://api-v3;
}