Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using nginx map directive to dynamically set proxy upstream

I'm trying to make my Nginx a bit more dry, as it's acting as a reverse proxy for nearly 20 servers. Here's what I'm trying to do, all the hostnames and stuff are changed/examples:

map $http_host $backend {
    baz.mydomain.com       hostname1:8080;
    foo.mydomain.com       192.168.1.10:8081;
    bar.mydomain.com       hostname2:1234;
    ham.mydomain.com       hostname2:5678;
}

server {
    listen                      443 ssl http2;
    server_name                 .mydomain.com;

    ssl_certificate             /usr/share/nginx/certs/mydomain.com.pem;
    ssl_certificate_key         /usr/share/nginx/certs/mydomain.com.key;

    location / {
        proxy_redirect          http:// https://;
        proxy_pass              http://$backend;
    }
}

The problem is that no matter what, this will always give a bad gateway error. I've tried a few variations and moving things around, with and without the wildcard server_name, with $host instead of $http_host but so far I can't get it working. Am I even going about this the right way? I'd really prefer not to have almost 20 separate virtual server entries in my config.

There isn't a whole lot of help in the nginx documentation about using map like this, and not a lot online except for one really old post that briefly mentioned something similar here: https://serverfault.com/questions/342309/how-to-write-a-dry-modular-nginx-conf-reverse-proxy-with-named-locations

like image 791
Tal Bull Avatar asked May 01 '18 18:05

Tal Bull


2 Answers

I got it figured out. The issue was that it didn't like having hostnames in the list. The hostnames are needed as all these addresses are allocated dynamically. This was solved with the upstream directive as follows:

upstream bazhost {server hostname1:8080;}
upstream foohost {server 192.168.1.10:8081;}
upstream barhost {server hostname2:1234;}
upstream hamhost {server hostname2:5678;}

map $http_host $backend {
    baz.mydomain.com       bazhost;
    foo.mydomain.com       foohost;
    bar.mydomain.com       barhost;
    ham.mydomain.com       hamhost;
}

server {
    listen                      443 ssl http2;
    server_name                 .mydomain.com;

    ssl_certificate             /usr/share/nginx/certs/mydomain.com.pem;
    ssl_certificate_key         /usr/share/nginx/certs/mydomain.com.key;

    location / {
        proxy_redirect          http:// https://;
        proxy_pass              http://$backend;
    }
}
like image 54
Tal Bull Avatar answered Sep 18 '22 16:09

Tal Bull


I figured the details out.
Basically, there is no DNS resolution if we set proxy_pass targets dynamically.

This won't work:

map $http_host $backend {
    default localhost:8080;
}

proxy_pass http://$backend;

But, this work:

map $http_host $backend {
    default 127.0.0.1:8080;
}

proxy_pass http://$backend;
like image 32
Curious Sam Avatar answered Sep 22 '22 16:09

Curious Sam