0

Problem

I am setting up Nginx-Proxy within docker upon my server. NginX-Proxy is meant to be directly accessible from the web and I would like to place a proxy in front of it.

Desired flow of connections

Ideally NginX(Host) should be the web facing server and reverse proxy back to the NginX-Proxy(Docker) which is served from a loopback address (127.0.0.2).

Essentially I require a proxy configuration for unrecognized sub-domains that passes these requests on to the NginX-Docker proxy, which hosts these sub-domains, any one of which may or may not have SSL enabled.

Homework

NginX supports proxying by various means, but none of them seem to quite fit my use case.

  1. I was hoping that NginX, on the host, might transparently proxy the docker instance using a configuration similar to that of Daniel James. This configuration performs SSL termination at the host which does not seem especially DRY, given that the docker containers, routed via the Nginx-Proxy, are themselves (possibly) doing SSL termination. Commonly this seems to be done with a 301 redirect (Due to limitations of older versions of Nginx) to force all incoming client traffic to use https and forcing the back end/upstream to use, exclusively, either HTTP or HTTPS. I would like to avoid termination at NginX(Host) and let the Nginx-Proxy handle it. The nice feature here is that one may filter domain name e.g. .domain.tld and *.domain.tld patterns. I have seen this referenced as a man in the middle proxy.

Man in the Middle Proxy

  1. NginX discusses a sort of compliments to this in it's Proxy-Protocol docs. I tried setting up a stream as shown but it seems one cannot filter the stream by domain name within the server block. Although it seems to forward both HTTP and HTTPS traffic without alteration. I understand this to be a sort of pass through proxy (Used more for load balancing/shaping).

Pass through proxy

  1. IP transparency seems to offer a solution that is more inline with my requirements. It has the down side that the NginX (Host) runners/thread must be run with root privileges and I'm not sure what the security implications are here (Not that I'm hosting top secret stuff but a hole is a hole). It also requires that one modify the docker containers routing/IP tables which I'm not sure is possible.

  2. The IP transparency doc also mentions DSR as an alternative means of proxying another server but then stats that NginX does not support this. I do not know what this is.

MWE

In my initial efforts I had hashed together the following which returns the site via HTTP alright but fails to return the site over HTTPS. This takes the MitM/reverse proxy strategy. I have left this in for consistency with the comments.

server {
 server_name .domain.tld;
 # note : .domain.tld matches both *.domain.tld and domain.tld
 listen X.X.X.X:80;
 listen [X:X:X:X:X:X:X]:80;
 listen X.X.X.X:443 ssl;
 listen [X:X:X:X:X:X:X]:443 ssl;

 server_tokens off;

 # Logging
 access_log /var/log/nginx/docker_proxy-access.log main;
 error_log /var/log/nginx/docker_proxy-error.log info;

 location / {
  proxy_pass $scheme://127.X.X.X; # Local service IP Address 
  # Alternatively : proxy_pass $scheme://$http_host$uri$is_args$args;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Host $host;
 }
}

How should I pass through the requested domain ? Using 127.X.X.X seems to confuse the upstream service which needs the domain name to resolve the docker container that provides a service (This was resolved in the comments). I'm not sure if I should configure an upstream service or use proxy_bind here ? Should I be setting the proxy_redirect attribute too ? Should I have separate server blocks for HTTP and HTTPS ?

  • You are already forwarding the host header, which should send through the domain name, assuming the client is sending it. If your upstream server is set up correctly to use the host header, then you need to check whether the client is sending the host header. Are you using the ip address to test in the browser? – ernest_k Dec 28 '17 at 9:43
  • @ErnestKiwele Thanks for the response, the host header is being sent properly e.g. curl X.X.X.X --header "Host:domain.tld". My question is about configuration strategies. Given NginX-proxy expects a direct connection to the internet/web facing how might one proxy it transparently via the hosts nginx. I'm about to rewrite the question though. – Carel Dec 30 '17 at 6:00
  • @ErnestKiwele I only just realized how you were trying to help. It turns out that the set_header Host $host; passes on the domain name requested by the client. Digital Ocean documents this nicely. – Carel Dec 30 '17 at 9:11
  • try to use traefik container for this – c4f4t0r Dec 30 '17 at 12:07
  • If I follow the Traefik documentation properly this seems to be an alternate to the Nginx-Proxy container. Which comes back to the question of "how should I proxy the Traefik/Nginx-Proxy container through my host container ?" (@c4f4t0r This seems to have the same drawback as a single Nginx-Docker proxy image in that you're exposing the docker socket to a web facing container, Nginx-Proxy allows one to split the service into two separate containers one with socket access and one with web access) It looks like it's more fleshed out then Nginx-Proxy is though so thanks for the pointer. – Carel Dec 30 '17 at 14:59
0

Since I first asked this I tried the suggestions of @c4f4t0r to use Traefik directly. This largely worked but Traefik is a level 7 proxy, that is it operates in the application layer and not on the transport layer. It cannot redirect any HTTPS traffic to a local webserver alongside it on the host. So SSL-Pass through (SNI) is not an option here. It seems one may perform SSL-Bridging, that is terminate the SSL connection at Traefik and create a new one to the local server.

What I missed from @Ernest Kiwele's advice was to check whether the upstream server handled the Proxy protocol, it does but I didn't know it did so nor had I enabled it at the time.

There is also a subtlety here, in the above configuration I essentially terminate the HTTP connection, modify the Header and establish a new one. This is done via the proxy protocol to the upstream service. As it's HTTP it is quite common to bridge the connection in this way. HTTPS additionally requires re-encryption at each termination/initiation stage. I was hoping to pass it through without decrypting/re-encrypting the traffic. As it turns out this is possible with NginX streams as shown in this answer.

To solve my problem I had to create both a http{server{}} server block and a stream{server{}} block. The former bridging and wrapping the HTTP connection within the proxy protocol at NginX. The latter wraps the HTTPS stream within a proxy protcol wrapper, determined by SNI, and passes it directly to the relevant backend, effectively by passing NginX and the need to bridge and de/encrypt.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.