23

We use Nginx as a reverse proxy to our web application server. Nginx handles our SSL and such but otherwise just acts as a reverse proxy.

We want to require a valid client cert for requests to /jsonrpc but not require them anywhere else. The best way we've found is to

server {
  listen       *:443 ssl;

  ssl on;
  ssl_certificate         /etc/nginx/server.crt;
  ssl_certificate_key     /etc/nginx/server.key;
  ssl_client_certificate  /etc/nginx/client-ca.crt;

  ssl_verify_client optional;

  location /jsonrpc {
    if ($ssl_client_verify != "SUCCESS") { return 403; }

    proxy_pass          http://localhost:8282/jsonrpc-api;
    proxy_read_timeout  90;
    proxy_redirect      http://localhost/ $scheme://$host:$server_port/;
  }
}

This works fine for most browsers, but some browsers such as Safari and Chrome-on-Android end up prompting the user to provide a client cert no matter where on the website they go.

How do we get Nginx to accept but not really care about a client cert everywhere except our /jsonrpc location?

Eli Courtwright
  • 449
  • 1
  • 5
  • 14

2 Answers2

11

Why not to try second server block instead? Code duplication is bad but sometimes unavoidable. I assume /jsonrpc represents an API so it can use its own subdomain if not already use it:

server {
  listen       *:443 ssl;
  server_name api.example.com;

  ssl on;
  ssl_certificate         /etc/nginx/server.crt;
  ssl_certificate_key     /etc/nginx/server.key;
  ssl_client_certificate  /etc/nginx/client-ca.crt;

  ssl_verify_client on;

  location =/jsonrpc {
    proxy_pass          http://localhost:8282/jsonrpc-api;
    proxy_read_timeout  90;
    proxy_redirect      http://localhost/ $scheme://$host:$server_port/;
  }
}

server {
  listen       *:443 ssl;

  ssl on;
  ssl_certificate         /etc/nginx/server.crt;
  ssl_certificate_key     /etc/nginx/server.key;
  ssl_client_certificate  /etc/nginx/client-ca.crt;

  ssl_verify_client off;

  location / {
    proxy_pass          http://localhost:8282/;
    proxy_read_timeout  90;
    proxy_redirect      http://localhost/ $scheme://$host:$server_port/;
  }
}
Anatoly
  • 566
  • 3
  • 16
  • This is what we'll probably end up doing if we can't figure out a way to put the right configuration all in the same ``server`` block. We haven't had this same issue when using Apache, so I'd hoped there was some setting that would work here. – Eli Courtwright Sep 13 '15 at 19:03
  • 1
    @EliCourtwright I know this question was a long time ago, but did you ever find a solution better than two server blocks? – N Jones May 31 '17 at 00:00
  • 2
    @NJones: unfortunately no, that's what we had to go with. – Eli Courtwright Jun 29 '17 at 18:51
  • 3
    What if everything must answer for the same domain www.example.com? Can an approach like this still work? – Freedom_Ben Mar 19 '20 at 22:24
  • 5
    The reason this works in Apache [is mentioned in passing in the `SSLVerifyClient` docs](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslverifyclient): *[…] In per-directory context [this directive] forces a SSL renegotiation with the reconfigured client verification level after the HTTP request was read but before the HTTP response is sent.* – Needless to say that nginx doesn't offer that option… – ntninja May 11 '21 at 20:26
  • 1
    @Freedom_Ben you can have two server blocks on the same domain but listening on two different ports. – Dmitriy Kuznetsov Aug 22 '23 at 19:33
6

Stumbled over this question while looking for something else.

Perhaps I misunderstood the question:

But shouldn't following work.

This has two location settings, but only one server setting.

server {
  listen       *:443 ssl;
  server_name api.example.com;

  ssl on;
  ssl_certificate         /etc/nginx/server.crt;
  ssl_certificate_key     /etc/nginx/server.key;
  ssl_client_certificate  /etc/nginx/client-ca.crt;

 ssl_verify_client optional;

  location =/jsonrpc {
    if ($ssl_client_verify != "SUCCESS") { return 403; }
    proxy_pass          http://localhost:8282/jsonrpc-api;
    proxy_read_timeout  90;
    proxy_redirect      http://localhost/ $scheme://$host:$server_port/;
  }

  location / {
    proxy_pass          http://localhost:8282/;
    proxy_read_timeout  90;
    proxy_redirect      http://localhost/ $scheme://$host:$server_port/;
  }
}
gelonida
  • 259
  • 3
  • 16