conditional routing with nginx based on referer

Debarshi DasGupta picture Debarshi DasGupta · Dec 11, 2018 · Viewed 9.3k times · Source

I need to route traffic based on the http request origin. I have two environments and we need to redirect every http request for "/us-en" to Environment1 and others to Environment2 using "$http_referer".

  1. Redirection based on location works.
location ~ /us-en { 
    proxy_pass Environment1; 
    proxy_set_header Host Environment1; 
} 
  1. With '$http_referer' the below option does not work. Request your suggestion on the same.
if ($http_referer ~ ^https?://dev.xyz.com/us-en){ 
    rewrite ^/us-en(/*)$ HOME_PAGE$1 break; 
    proxy_pass Environment1; 
}
Error: nginx: [emerg] "proxy_pass" directive is not allowed here in /opt/nginx/conf/nginx.conf.

Note: By default all the traffic goes to Environment2 as an upstream configuration is present.

Answer

Ivan Shatsky picture Ivan Shatsky · Dec 11, 2018
    # needed if your proxy destination specified with domain name instead of IP address
    resolver 8.8.8.8;

    location /home/ {
        proxy_set_header Host HOST1;
        # setup other proxied headers if needed
        if ($http_referer ~ ^https?://dev.xyz.com/home) {
            rewrite ^/home(/.*)$ HOME_PAGE$1 break;
            proxy_pass https://HOST1:8080; # this can be specified by IP address
        }
    }

With such configuration requests to your_domain.com/home/path/file from dev.xyz.com/home/... (but not from dev.xyz.com/any/other/path!) will be proxied to https://HOST1:8080/HOME_PAGE/path/file. If you specify your proxy destination with domain name instead of IP address, you'll need to specify the additional parameter resolver in your server config. You can use your local name server if you have one, or use something external like Google public DNS (8.8.8.8) or DNS provided for you by your ISP. Anyway such configuration leads to additional DNS lookups, so if you can, specify your proxy destination with IP address.

Update

There is another way to do it with the valid_referers directive:

    # needed if your proxy destination specified with domain name instead of IP address
    resolver 8.8.8.8;

    location /home/ {
        proxy_set_header Host HOST1;
        # setup other proxied headers if needed
        valid_referers example.com/home;
        if ($invalid_referer = "") {
            rewrite ^/home(/.*)$ HOME_PAGE$1 break;
            proxy_pass https://HOST1:8080; # this can be specified by IP address
        }
    }

Update @ 2020.11.11

Besides this answer somehow achieved a score of 5, the given solution has an extremely bad design (it isn't a good approach to have different content handlers in the location and the nested if block; moreover, having an if block with any directive other than from the nginx rewrite module should be avoided if possible) and won't work at all on early nginx versions (I wanna cry when I look at some of my early answers). An original OP question was

The logic should be like below but has some syntax mistakes.

if ($http_origin ~ '^http?://(dev.xyz.com/home)') {
    set $flag 'true';
}

if ($flag = 'true') {
    location /home/ {
        proxy_pass  "https://HOST1:8080/HOME PAGE/";        
    }
}else{
    Do Not proxy pass
}

It is unclear what do not proxy pass means. If it means returning some HTTP error (for example, HTTP 403 Forbidden), it can be done with the following configuration:

location /home/ {
    if ($http_referer !~ ^https?://dev.xyz.com/home) {
        return 403;
    }
    rewrite ^/home(/.*)$ HOME_PAGE$1 break;
    proxy_set_header Host HOST1;
    # setup other proxied headers if needed
    proxy_pass https://HOST1:8080; # this can be specified by IP address
}

If do not proxy pass means to serve the request locally, the solution is more complex:

map $http_referer $loc {
    ~^https?://dev.xyz.com/home    loc_proxy;
    default                        loc_local;
}

server {
    ...
    location /home/ {
        try_files /dev/null @$loc;
    }
    location @loc_proxy {
        rewrite ^/home(/.*)$ HOME_PAGE$1 break;
        proxy_set_header Host HOST1;
        # setup other proxied headers if needed
        proxy_pass https://HOST1:8080;
    }
    location @loc_local {
        rewrite ^/home(/.*)$ HOME_PAGE$1 break;
        root /path/to/required/page;
        ...
    }

The try_files /dev/null @the_named_location; trick is taken from this excellent answer.

However now the edited OP's question states for a different requirements, which also could be achieved with the map directive help:

map $http_referer $environment {
    ~^https?://dev.xyz.com/home    Environment1;
    default                        Environment2;
}

server {
    ...
    location /home/ {
        rewrite ^/home(/.*)$ HOME_PAGE$1 break;
        proxy_set_header Host $environment;
        # setup other proxied headers if needed
        proxy_pass https://$environment;
    }