I am trying to configure client certificate verification (mTLS) in nginx and test it with curl.

Important part of my nginx config:

server {
    listen         443 ssl default_server;
    listen         [::]:443 ssl http2 ipv6only=on;
    server_name    example.com;

    ssl_protocols TLSv1.2;

    ssl_certificate /certs/latest_server_cert;
    ssl_certificate_key /certs/latest_server_key;
    ssl_client_certificate /certs/root_only;

    ssl_verify_client on;
    ssl_verify_depth 2;

Note that /certs/root_only only contains the root CA for my client certificate.

ssl_certificate has the server cert and intermediate cert in it.

My understanding is that for client certificate verification, that on the server-side, you simply need the root CAs (similar to a browser has the root CAs for server-side certs...i.e. the inverse of client certificate verification) and that the client should present both the cert and any intermediates which are verified with the CA in ssl_client_certificate.


With curl, it seems to not send the intermediate even if the cert and intermediate is chained in the file passed to the --cert option. And I get a 400 error from nginx and the nginx log has:

client SSL certificate verify error: (21:unable to verify the first certificate) while reading client request headers

I was able to work around this (sort of) by using --cacert which I assumed was only used for verifying the server cert. However, it seems to use the cert file specified with this option to send certs to the server for client certificate verification because if I append my client cert's intermediate to the file specified by --cacert, it works. However, it needs the CA chain for both the client cert and the server cert so it seems that --cacert is overloaded in it's usage.

Similarly with openssl s_client, if I have a chained cert/inter passed to -cert then the intermediate is not sent to the server. I have to use -CAfile. Interesting difference between curl and openssl s_client though: curl requires that the --cacert file have both the roots for the server side cert validation and the intermediate for the client side cert. However, openssl s_client only needs the additional certificate to complete the chain for client cert verification (i.e. the intermediate). I guess this means that it is still using the system trust CA for server side validation. At let with openssl s_client the -CAfile documents that it is used for client/server cert chain validation:

-CAfile file

A file containing trusted certificates to use during server authentication and to use when attempting to build the client certificate chain.

However, if I add the intermediate to my nginx config cert chains (ssl_client_certificate) such that it has the roots/inters for the client certificates, then my clients (curl or openssl s_client) can connect with purely the leaf client certificate.

While the above method works, it's not as maintainable and I'd prefer that my clients send nginx the cert and intermediates.

Any advice for better setup/configuration or information on my misunderstanding on these topics?

Your Answer

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

Browse other questions tagged or ask your own question.