Nginx, vhosts and client certificates

Using client certificates with multiples vhosts might show limitations.

If a deployment consists of:

  • A first layer of Nginx reverse proxies, for TLS termination;
  • Connected, with TLS client certificates, to a set of backend "application" servers;
  • And the Host / SNI headers sent to the backend don't match that sent to the client (for instance when using a blue/green deployment at the front layer);
  • And the backend servers host several vhosts with ssl_verify_client enabled on the same IP;

Then users might encounter a 421 Misdirected Request error (at least under nginx version 1.14.2.

This is quite reasonable: Nginx receives a Host: header matching the public name (let's say public.example.com), but sees a TLS connection to SNI host backend-www-blue.example.com.

Since the TLS hostname doesn't match the requested Host header, it assumes that the client is reusing a connection intended for another vhost, and the client certificate verification isn't valid.

Solutions

Possible solutions:

  • Use a dedicated IP address for each client-SSL-enabled vhost;

  • Don't send the public Host: header, use the internal one instead;

  • Perform hostname-based request routing in code:

    map $ssl_server_name $dyn_upstream {
      hostnames;
      default             "none";
      public.example.com  "public";
      priv.example.com    "priv";
    }
    
    server {
      listen 443 ssl;
      server_name foo.example.com bar.example.com;
    
      if ($dyn_upstream = "none") {
        # Early request termination
        return 444;
      }
    
      location / {
        upstream http://$dyn_upstream/;
      }
    }
    
  • Stack two vhosts: the first one performs client certificate verification, and adds metadata to the request headers; an upstream instance (could be the same nginx process) will then perform perform the routing, examining the forwarded headers for client checks.