When running Nginx in containerized environments like Kubernetes on GCP, client IP addresses are typically only available through the X-Forwarded-For (XFF) header. While basic IP filtering works for individual addresses, managing CIDR ranges requires a more sophisticated approach.
The XFF header often contains multiple IPs in a comma-separated format (client, proxy1, proxy2). The leftmost non-internal IP is typically the original client. For GCP environments, the format usually appears as:
X-Forwarded-For: client_ip, load_balancer_ip
Nginx's geo
module provides the most efficient way to handle IP ranges. Here's a complete implementation:
geo $client_ip {
default 0;
123.233.233.0/24 1;
192.168.1.0/24 1;
10.0.0.0/8 0; # Deny internal ranges
include /etc/nginx/conf.d/ip-ranges.conf;
}
map $http_x_forwarded_for $real_client_ip {
~^([^,]+) $1;
default $remote_addr;
}
server {
listen 80;
set $allow false;
if ($client_ip = 1) {
set $allow true;
}
if ($allow = false) {
return 403;
}
# Your regular configuration
}
For more complex scenarios, you can use regex patterns with the XFF header:
set $block_me 0;
# Block known bad actors
if ($http_x_forwarded_for ~* "(123\.45\.67\.[0-9]+|evilbot\.com)") {
set $block_me 1;
}
# Allow only from specific countries
geoip_country /etc/nginx/geoip/GeoIP.dat;
if ($geoip_country_code != US) {
set $block_me 1;
}
In GKE environments, you'll need to account for:
- Cloud Load Balancer IPs in the XFF chain
- Pod-to-pod communication IPs
- GCP health check IP ranges
Here's a GKE-optimized configuration snippet:
map $http_x_forwarded_for $forwarded_client_ip {
~^(?[^,]+),?.*$ $client;
}
geo $valid_client {
default 0;
# Include your allowed CIDR ranges
include /etc/nginx/allow-ranges.conf;
# Exclude GCP internal IPs
10.128.0.0/20 0;
35.191.0.0/16 0;
130.211.0.0/22 0;
}
When running Nginx in a Kubernetes cluster on GCP, client IP addresses are typically passed through the X-Forwarded-For
header rather than appearing in the standard remote address variable. While simple IP-based restrictions work for individual addresses, managing entire IP ranges requires a more sophisticated approach.
The X-Forwarded-For
header often contains comma-separated IP addresses, with the original client IP being the leftmost entry:
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip
Here's a complete Nginx configuration snippet that implements CIDR-based IP filtering:
geo $real_client_ip {
default 0;
192.168.1.0/24 1;
10.0.0.0/8 1;
172.16.0.0/12 1;
# Add more CIDR ranges as needed
}
map $http_x_forwarded_for $real_client_ip {
default "";
~^(?<first_ip>[^,]+) $first_ip;
}
server {
listen 80;
if ($real_client_ip = "") {
set $real_client_ip $remote_addr;
}
if ($real_client_ip ~ "^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$") {
set $block_access 0;
} else {
set $block_access 1;
}
if ($block_access = 1) {
return 403;
}
location / {
# Your normal configuration
}
}
For more complex scenarios, consider using Nginx's GeoIP module:
load_module modules/ngx_http_geoip_module.so;
http {
geoip_country /etc/nginx/geoip/GeoIP.dat;
map $http_x_forwarded_for $real_ip {
~^(?P<first>[^,]+) $first;
default $remote_addr;
}
server {
if ($geoip_country_code = CN) {
return 403;
}
# Additional geo-based rules
}
}
In complex environments with multiple proxy layers, use this enhanced parsing approach:
map $http_x_forwarded_for $client_ip {
"~*(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})(?:,|$)" $ip;
default $remote_addr;
}
geo $client_ip $allowed_ip {
ranges;
default 0;
10.0.0.0-10.255.255.255 1;
192.168.0.0-192.168.255.255 1;
# Add more ranges
}
server {
if ($allowed_ip = 0) {
return 403;
}
}
For large IP range lists, consider these optimizations:
- Use
geo
with CIDR notation instead of regex patterns - Place frequently matched ranges at the top
- Consider using separate configuration files for IP ranges
- Benchmark with
nginx -T
to test configuration