Nginx: Proper way to use different reverse proxy upstream based on user-agent without using if

Written by - 0 comments

Published on February 24th 2020 - Listed in Nginx


Using if in Nginx's configs is considered bad, even called evil (see if is evil). Although a lot of Nginx configs exist where conditions are set using if (and they mostly work, too!), it is encouraged to use another approach.

In this particular scenario, traffic being identified from a certain user-agent should be using a different backend/upstream than the default traffic. An if rule would have been very easy:

server {
[...]
    if ($http_user_agent ~ mybot ) {
        proxy_pass http://127.0.0.1:8090;
    }

    proxy_pass http://127.0.0.1:8080;
[...]
}

But once again: We don't want to use if!

map to the rescue!

In the past an almost similar solution had to be achieved; see article different proxy_pass upstream depending on client ip address in Nginx. The difference? A client IP address or range should use a "geo" map (to be able to use ranges). User agents are strings therefore map can be used without a problem.

The map should be defined before the server context (definitely not within):

map "$http_user_agent" $targetupstream {
  default        http://127.0.0.1:8080;
  "~^mybot"      http://127.0.0.1:8090;
}

What this does is:

  • Nginx takes the User-Agent header from the HTTP request (using $http_user_agent variable) and compares the value with column 1 of the map.
  • In this map, column 1 has two entries: "default" and "~^mybot".
  • If the user-agent from the request matches the regular expression of "~^mybot", it will save column 2 of that match (http://127.0.0.1:8090) as variable $targetupstream. This variable can be used in configs following this map definition.
  • If the user-agent does not match any entries, Nginx will use the "default" entry (saving http://127.0.0.1:8080 as $targetupstream variable).

Dynamic proxy_pass

The map definition above means that the upstream is now stored in a variable $targetupstream. This means: The upstream URL is now dynamic! Nginx will do the mapping according to (here) the request's user-agent. All that needs to be done is to use the variable as proxy_pass:

# HTTPS track
server {
[...]
  location / {
    include /etc/nginx/proxy.conf;
    proxy_set_header X-Forwarded-Proto https;
    proxy_pass $targetupstream;
  }
[...]
}

Pretty awesome, right? Oh, and guess what? We didn't even use if! =)


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.