Nginx geoip redirect for first time only leads to redirect loop

geoipnginxrewrite

I'm trying to configure nginx to perform geoIP checks on first-time visitors only.

The idea is that first-time visitors should be redirected to the page version which has been customised for their language/country.

I'm able to check whether or not a certain cookie exists. If it does, then nginx can proceed and not redirect.

This set up means that all first-time visitors will be redirected to what it's likely to be the most appropriate version of the page for their country/language. And at the same time, those users that wish to, can still browse freely all versions available afterwards.

I believe Google recommends this type of set up for multilingual/multisite webpages too.

The problem is that I get a redirect loop if the rewrite directive is placed outside the root location. Yet, because the US version is the same as /root, rewrites within locations have proven even more difficult to configure.

The geo_IP values are passed OK via fastcgi to php if no rewrite is specified, so I don't think there is anything wrong with the setup apart from the rewrite itself.

This is my config:

map $geoip2_data_country_code $country_url {
  default example.com; ##This is the US version
  GB      example.com/uk;
  CA      example.com/ca;
}

server {
    listen 443 ssl spdy default_server;
    server_name example.com;
    root /var/www/html;

    if ($http_cookie !~ "cookie_to_check") {        
       rewrite ^ https://$country_url break;
       #Appending $request_uri to the rewrite yields very similar results
    }    


    location / {
    try_files $uri $uri/ /index.php?$args; 
    }

    location /uk {
    try_files $uri $uri/ /uk/index.php?$args;   
    }

    location /ca {  
    try_files $uri $uri/ /ca/index.php?$args;   
     }

##Other locations
}

Best Answer

After a lot of trial and error, this is the configuration that would work and perform as expected.

The problem with my previous version was double:

  • Nginx rewrites outside locations were messy and they would just refuse to work by creating nasty loops
  • Nginx was checking the existence of a cookie which is set by a php application behind nginx. Thanks to @AlexeyTen for pointing this out. Setting an extra cookie conditionally with nginx did the trick

    map $geoip2_data_country_code $country_url {
         default example.com; ##This is the US version
         GB      example.com/uk;
         CA      example.com/ca;
    }
    
    server {
        listen 443 ssl spdy default_server;
        server_name example.com;
        root /var/www/html;
    
        location / {
            try_files $uri $uri/ /index.php?$args; 
            if ($http_cookie !~ "country=set") {                            
                add_header Set-Cookie "country=set;Max-Age=31536000";
                rewrite ^ $scheme://$country_url$request_uri break;
                }
        }
    
        location /uk {
            try_files $uri $uri/ /uk/index.php?$args;  
            if ($http_cookie !~ "country=set") {                            
                add_header Set-Cookie "country=set;Max-Age=31536000";
                rewrite ^ $scheme://$country_url$request_uri break;
                } 
        }
    
        location /ca {  
            try_files $uri $uri/ /ca/index.php?$args;   
            if ($http_cookie !~ "country=set") {                            
                add_header Set-Cookie "country=set;Max-Age=31536000";
                rewrite ^ $scheme://$country_url$request_uri break;
                }
        }
    
        ##Other locations
        }