Mini Guide: Quick and easy Client SSL Validation (with OpenSSL and Nginx)

Mini Guide: Quick and easy Client SSL Validation (with OpenSSL and Nginx)
Photo by Georg Bommeli / Unsplash

I run a number of services from home, for my own convenience, that I want to be able to access remotely, securely, on the go, without the cumbersome need for usernames and passwords all the time.

This mini-guide looks at securely identifying the end user with client SSL certificates instead of conventional credentials.

Some of my other posts cover aspects like remote access and the complete SSL setup.


This is the very simple version.

There is no CA and it has potential flaws, but it works for me and this use case as I am the only client - if the certificate were to be compromised I can regenerate the key and cert and be fine.

For a multi user environment a private CA would be required but that is outside the scope of this post.


The services running include tools to view the CCTV streams, manage doorbell notifications, access local devices, code-server and more.

When I'm on the move, or not at home (for example in the office) I want to be able to quickly access these services, but still securely - I don't want any randomers finding and accessing it so something totally open is not an issue, firewall rules won't do as IP Addresses change when mobile, VPN is clunky on the phone, etc.

The ideal thing for me is verifying the client device, which is where client SSL verification comes in.

Client SSL Certificates

We're mostly used to server SSL authentication/verification where the server has a certificate that can be resolved back to a trusted entity that granted it after confirming it's all legitimate (usually by DNS, .well-known or email).

In the case of Client SSL Certificates, the device or browser (i.e. client) can present an SSL certificate for the server to check and validate. If the certificate is valid according to the server (not expired and matching the expected cert/chain) it can permit the traffic.


We need a private and public key pair, in the SSL world this is usually a key (private) and certificate (public).

First, the key

openssl genrsa -out client.key 4096

Then, the certificate

openssl req -new -x509 -days 3650 -key client.key -out client.crt

It's naughty, but this one is valid for 10 years as it's a pain to reinstall on all the devices I have regularly.

The fields don't matter so I enter a . to keep it blank mostly, even the fqdn isn't checked:

Finally, the pfx

openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt	

Having a pfx to import has a few benefits, firstly its what browsers and mobile devices prefer, second - you can password protect it with a passphrase, so if the file itself is compromised you have a layer of protection.


For each site that needs securing...

Before any location directives we need to be sure the SSL client certificate is verified using:

if ($ssl_client_verify != SUCCESS) {
  return 403;

And we also need to specify the certificate to check against:

ssl_client_certificate /etc/nginx/certs/client.crt;


As the browser (client) may (or is highly unlikely to randomly offer up a client cert) we need to make the challenge optional:

ssl_verify_client optional;

Otherwise we'll be denied access completely.

The if above guards against the verification then failing when a certificate is presented.

A complete config is as follows, the letsencrypt aspects are a topic for another post:

The Browser (client)

Without any certificate we get a 403:

With the wrong certificate, not much better:

In Chrome, a client certificate can be imported from the Security settings:

Once imported the org name should be listed:

I've found that a complete browser restart is often required to refresh the certificates at times.

The certificate challenge will prompt the browser to confirm the certificate to use:

Once the correct certificate is selected the site is accessible:

Certificates on iOS (and known issues)

The client certificates can be used on iOS but they only work when the page is opened in Safari - on the plus side any 'pinned' sites to the home screen will work fine.

The easiest way that I've found to import them is to open them in the Mail app (so I email them to my apple email) and open the pfx attachment from there.

You  can them import the profile from settings.

Note: If you receive an incorrect password error despite being sure the password is correct it is likely an OpenSSL version issue as discussed here.

OpenSSL 3.x changed the default algorithm and it's not compatible with macOS SSL libraries which are no longer staying current with OpenSSL due to breaking changes such as this.  

Fortunately, OpenSSL added a -legacy flag to revert to the previous algorithm.  Add the -legacy flag after your -export flag parameter in your openssl command string.  

The alternative is to downgrade openssl to 1.x