Nginx Regex Matching: Case-Sensitive (~) vs Case-Insensitive (~*) Operator Differences


2 views

In Nginx configuration, the ~ and ~* operators perform regular expression matching with a crucial distinction:

# Case-sensitive matching (exact case required)
if ($uri ~ "^/admin/") {
    return 403;
}

# Case-insensitive matching (any letter case accepted)
if ($uri ~* "^/admin/") {
    return 403;
}

The underlying implementation uses PCRE (Perl Compatible Regular Expressions) with these flags:

~  → No special flags (case-sensitive by default)
~* → Adds PCRE_CASELESS flag

Case-insensitive matching generally has slightly higher CPU overhead due to:

  • Additional character case conversions
  • Larger potential match set

When to use ~:

# Matching specific API endpoints
location ~ "^/api/v[0-9]/transactions" {
    proxy_pass http://backend;
}

When to use ~*:

# File extension matching (common with mixed-case extensions)
location ~* "\.(jpg|jpeg|png|gif)$" {
    expires 30d;
}
# This won't match "/Admin" or "/ADMIN"
if ($http_host ~ "admin.example.com") {
    # ...
}

# This will match all case variations
if ($http_host ~* "admin.example.com") {
    # ...
}

Remember that Nginx variables like $host are typically lowercase, while $http_* variables preserve original case.

# Complex case-sensitive match with capture groups
if ($request_uri ~ "^/users/([0-9]+)/profile$") {
    set $user_id $1;
}

# Case-insensitive domain whitelisting
if ($http_referer ~* "(www\.)?(example|foobar)\.(com|net)") {
    # ...
}

In Nginx configuration, both ~ and ~* are regex matching operators, but they handle case sensitivity differently:

# Case-sensitive match (only matches exact case)
if ($http_referer ~ www.Foobar.net) {
    # matches "www.Foobar.net" but not "www.foobar.net" or "WWW.FOOBAR.NET"
}

# Case-insensitive match
if ($http_referer ~* www.foobar.net) {
    # matches all variations: "www.foobar.net", "WWW.FOOBAR.NET", "www.FooBar.Net"
}

Use ~ when:

  • You need exact case matching (security headers, sensitive paths)
  • Matching case-sensitive APIs or protocols

Use ~* when:

  • Handling user-generated content (URLs, form inputs)
  • Matching domain names or file extensions (.jpg, .JPG, .jPg)

Case-sensitive matches (~) are generally slightly faster (5-15% in benchmarks) because they don't require case normalization. However, the difference is negligible for most use cases.

# Multiple domain matching (case-insensitive)
if ($host ~* "(example\.com|test\.org)") {
    add_header X-Matched-Domain $host;
}

# File extension whitelisting
location ~* "\.(jpg|jpeg|png|gif)$" {
    expires 30d;
}

# Case-sensitive API routing
location ~ "^/api/v[1-3]/" {
    proxy_pass http://backend;
}
  1. Using ~* for security checks (attackers may bypass with case variation)
  2. Forgetting that ~ matches substrings unless anchored:
    # Without anchors (matches "evilfoobar.com")
    if ($host ~ foobar) { ... }
    
    # With anchors (matches exactly "foobar.com")
    if ($host ~ ^foobar\.com$) { ... }
    
  • Always use ^ and $ anchors when matching entire strings
  • For security checks, prefer ~ with exact matches
  • Document which regex operator you're using in complex configurations