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.