proxy_pass based on GET args in nginx

A bit of a non-trivial task, as it turns out the location directive will not match anything after the ? in the URL. Furthermore, attempting to work around this issue by putting the proxy_pass inside an if block will likely result in an error like this one:
nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /etc/nginx/conf.d/domain.com.conf:23

There is a workaround, it’s not pretty, but it should do the trick (giving you time to hopefully refactor the URLs in your app to something more sensible). In the example below, I needed to match a couple of MD5 hashes and proxy those requests to host B while proxying the rest of the requests to host A.

location / {
		error_page 418 = @myredir;
		if ( $args ~ "c=[0-9a-f]{32}" ) { return 418; }
		
		proxy_pass http://host_a:80;
		proxy_set_header Host yourdomain.com;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_connect_timeout 120;
		proxy_send_timeout 120;
		proxy_read_timeout 180;
}

location @myredir {
		proxy_pass http://host_b:80;
		proxy_set_header Host yourdomain.com;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_connect_timeout 120;
		proxy_send_timeout 120;
		proxy_read_timeout 180;
}

Note that the error code we are “throwing” is actually a part of a old April fool’s RFC. You can use any other code that is not a valid HTTP response code.

Quick tip: emulating plain text files directly in nginx config

Every once in a while I end up setting up another reverse proxy/rewrite engine with nginx. In order to keep such configs easily portable you could do something like this:

	location =/robots.txt {
		add_header              Content-Type text/plain;
		return 200 "User-agent: *
Disallow: /
";
	}

This eliminates the need to distribute an actual robots.txt file with the example above. If you intend to serve HTML instead, obviously change the MIME type to text/html as well.