How to Configure Nginx to Serve Different Content Based on Client IP Address/Subnet


2 views

In enterprise environments, we often need to serve different content based on whether the request comes from internal or external networks. This could be for security reasons, content differentiation, or A/B testing purposes. Nginx provides excellent capabilities for this through its geo module and if conditions.

The most efficient way to implement this is by using Nginx's built-in geo module to classify IP addresses:


http {
    geo $client_type {
        default       external;
        10.0.0.0/16   internal;
    }

    server {
        listen 80;
        server_name company.com;

        location /page.html {
            try_files /${client_type}.html =404;
        }
    }
}

For more complex scenarios, the map directive offers better flexibility:


http {
    map $remote_addr $client_type {
        default       external;
        "~^10\."      internal;
    }

    server {
        listen 80;
        server_name company.com;

        location /page.html {
            if ($client_type = internal) {
                rewrite ^ /internal.html last;
            }
            rewrite ^ /external.html last;
        }
    }
}

When dealing with multiple internal subnets, comma-separate them in the geo block:


geo $client_type {
    default            external;
    10.0.0.0/16        internal;
    192.168.1.0/24     internal;
    172.16.0.0/12      internal;
}

For high-traffic sites, consider these optimizations:

  • Place geo blocks in nginx.conf outside server contexts
  • Use variables sparingly in location blocks
  • Consider using nginx's split_clients for A/B testing scenarios

After implementing, test with these commands:


nginx -t
curl -H "Host: company.com" http://localhost/page.html
curl -x 10.0.0.1:80 http://company.com/page.html

When building corporate web applications, we often need to serve different content based on whether users are accessing from internal networks or external internet connections. Nginx's powerful geo module and map directives provide elegant solutions for this IP-based routing requirement.

Here's a complete implementation that handles both IPv4 and IPv6 addresses:


http {
    # Define IP ranges
    geo $internal_user {
        default 0;
        10.0.0.0/16 1;
        192.168.0.0/24 1;
        ::1/128 1; # IPv6 localhost
        fd00::/8 1; # IPv6 private range
    }

    server {
        listen 80;
        server_name company.com;

        location /page.html {
            # Check geo variable
            if ($internal_user) {
                rewrite ^ /internal.html last;
            }
            rewrite ^ /external.html last;
        }

        location = /internal.html {
            internal;
            root /var/www/corporate;
            try_files $uri =404;
        }

        location = /external.html {
            root /var/www/public;
            try_files $uri =404;
        }
    }
}

For production environments, consider these optimizations:


# More maintainable CIDR range management
geo $internal_user {
    ranges;
    default 0;
    10.0.0.0-10.0.255.255 1;
    192.168.0.0-192.168.0.255 1;
}

# Cache geo lookups for performance
geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb {
    $geoip2_data_country_code country iso_code;
    auto_reload 60m;
}

Verify your configuration works as expected:


# Test from internal network
curl -H "Host: company.com" http://10.0.0.34/page.html

# Test from external network
curl -H "Host: company.com" http://8.8.8.8/page.html

Important security considerations when implementing IP-based routing:

  • Always include internal directive for private content
  • Consider X-Forwarded-For headers if behind load balancers
  • Implement fail-closed behavior for geo IP database failures