NGINX "If Is Evil"

The infamous NGINX if directive can cause unexpected behavior, memory leaks, and crashes. Learn why and what to use instead.

Updated: January 2025 * 10 min read

Why "If Is Evil"

The if directive in NGINX is not a general-purpose conditional statement like in programming languages. It's part of the rewrite module and behaves in non-intuitive ways that can cause serious problems:

The Problems with if

  • Creates a pseudo-location - if blocks create new configuration contexts
  • Directive inheritance is broken - Some directives don't work inside if
  • Can cause segfaults - Certain combinations crash NGINX
  • Evaluation timing is confusing - Executes at unexpected points
  • Only certain directives are safe - Most directives cause problems

The Technical Problem

When NGINX processes a request, it goes through multiple phases. The if directive evaluates during the rewrite phase, but creates a nested location that affects content handling phases. This mismatch causes unexpected behavior.

# This looks logical but DOESN'T work as expected
location / {
    set $true 1;

    if ($true) {
        # This block creates a new pseudo-location
        # Some directives here won't work!
    }

    # Directives here might not execute
    # because the if block "captured" the request
}

Dangerous if Patterns

Pattern 1: if with proxy_pass

# proxy_pass inside if is BROKEN
location / {
    if ($request_uri ~ ^/api/) {
        proxy_pass http://api-backend;
    }
    proxy_pass http://default-backend;
}

# Result: Unpredictable behavior, wrong backend, or crashes

Pattern 2: if with try_files

# try_files doesn't work inside if
location / {
    if (-f $request_filename) {
        # This seems logical...
    }
    try_files $uri $uri/ /index.html;
}

# Result: try_files may be ignored entirely

Pattern 3: Setting Headers in if

# Headers might not be set
location / {
    if ($request_method = POST) {
        add_header X-Post-Request true;
    }
    proxy_pass http://backend;
}

# Result: Header may or may not appear depending on other factors

Pattern 4: Multiple if Blocks

# Multiple ifs interact in unpredictable ways
location / {
    set $backend "default";

    if ($http_x_custom) {
        set $backend "custom";
    }

    if ($arg_override) {
        set $backend "override";
    }

    # Which backend? It's complicated...
}

When if IS Safe

There are exactly two safe uses of if inside a location:

1. return (immediate response)

# SAFE: return terminates request processing
location / {
    if ($request_method = OPTIONS) {
        return 204;
    }

    if ($http_user_agent ~* "badbot") {
        return 403;
    }

    proxy_pass http://backend;
}

2. rewrite ... last

# SAFE: rewrite last exits the location
location / {
    if ($arg_redirect) {
        rewrite ^ /redirected last;
    }

    # Normal processing continues if no redirect
}
The Rule: Only use if with directives that terminate request processing in that location: return and rewrite ... last.

Safe Alternatives

Use map Instead

# Define logic with map (in http block)
map $request_uri $backend {
    default         http://default-backend;
    ~^/api/         http://api-backend;
    ~^/admin/       http://admin-backend;
}

# Use the variable in location
server {
    location / {
        proxy_pass $backend;
    }
}

Use Multiple Locations

# Instead of if, use separate locations
location /api/ {
    proxy_pass http://api-backend;
}

location /admin/ {
    proxy_pass http://admin-backend;
}

location / {
    proxy_pass http://default-backend;
}

Use try_files for File Checks

# Instead of if (-f $request_filename)
location / {
    try_files $uri $uri/ @fallback;
}

location @fallback {
    proxy_pass http://backend;
}

Use error_page for Conditional Routing

# Route based on file existence
location / {
    try_files $uri $uri/ =404;
    error_page 404 = @backend;
}

location @backend {
    proxy_pass http://backend;
}

Use geo for IP-Based Decisions

# Instead of if checking $remote_addr
geo $is_internal {
    default         0;
    192.168.0.0/16  1;
    10.0.0.0/8      1;
}

map $is_internal $internal_backend {
    1    http://internal-backend;
    0    http://public-backend;
}

server {
    location / {
        proxy_pass $internal_backend;
    }
}

Common Refactoring Examples

Example 1: Blocking User Agents

# BAD: if for blocking
location / {
    if ($http_user_agent ~* "badbot|scraper") {
        return 403;
    }
    proxy_pass http://backend;
}
# GOOD: map + return in separate location
map $http_user_agent $bad_bot {
    default         0;
    ~*badbot        1;
    ~*scraper       1;
}

server {
    if ($bad_bot) {
        return 403;
    }
    # Note: This if is in server context, which is safer
    # Or use a dedicated location:

    location / {
        proxy_pass http://backend;
    }
}

Example 2: Maintenance Mode

# BAD: file check with if
location / {
    if (-f /etc/nginx/maintenance.flag) {
        return 503;
    }
    proxy_pass http://backend;
}
# GOOD: Use a variable set externally
# In nginx.conf or via include:
set $maintenance 0;  # Set to 1 to enable

server {
    if ($maintenance) {
        return 503;
    }

    location / {
        proxy_pass http://backend;
    }
}

Detecting with Gixy

Gixy detects dangerous uses of the if directive:

$ gixy /etc/nginx/nginx.conf

==================== Results ====================

[if_is_evil] If is evil... when used in location context.
  Severity: HIGH
  Description: Using "if" in location context can cause unexpected
               behavior or segfaults.
  Reason: Directive "proxy_pass" may not work as expected inside if.
  Pseudo config:
      location / {
          if ($arg_backend) {
              proxy_pass http://backend;
          }
      }
  File: /etc/nginx/conf.d/site.conf
  Line: 12

==================== Summary ====================
Total issues: 1 (High: 1)

Quick Reference

Avoid Use Instead
if + proxy_pass map + variable in proxy_pass
if + add_header map + always clause
if (-f ...) try_files
if ($uri ...) Multiple location blocks
if ($remote_addr ...) geo block

Further Reading