ssl client auth trouble

j94305 nginx-forum at
Fri Aug 30 22:58:20 UTC 2019

I'm a big fan of throw-away certificates, i.e., self-signed certificates you
may dispose of any time. It seems, the generation of proper certificates is
still a mystery to some, so let me briefly include a recipe how to create

Create a cert-client.conf of the following form:

# Client certificate request

[ req ]
default_bits		= 4096			# RSA key size
encrypt_key		= yes			# Protect private key
default_md		= sha256		# MD to use
utf8			= yes			# Input is UTF-8
string_mask		= utf8only		# Emit UTF-8 strings
prompt			= no			# Prompt for DN
distinguished_name	= email_dn		# DN template
req_extensions		= email_reqext		# Desired extensions

[ email_dn ]
0.domainComponent       = "com"
1.domainComponent       = "example"
2.domainComponent       = "project"
organizationName	= "{{ORG}}"
organizationalUnitName  = "{{CUSTOMER}}"
commonName              = "{{NAME}}"
emailAddress            = "{{EMAIL}}"

[ email_reqext ]
keyUsage                = critical,digitalSignature,keyEncipherment
extendedKeyUsage        = emailProtection,clientAuth
subjectKeyIdentifier    = hash
subjectAltName          = email:{{EMAIL}}

Replace and the stuff in {{...}} accordingly. I keep the
domain components usually fixed for a project, while the rest is dynamically
replaced with sed.

Assume, you have an instance of cert-client.conf replaced properly, and it's
in wherever the value of $conf points to. Assume $name is the name you want
to give to this certificate. Then you create it with

openssl req -new -newkey rsa:4096 -x509 -days 365 -nodes -config $conf
-sha256 \
	-passout "pass:" -out "$name.pem" \
	-keyout "$name.key"
openssl pkcs12 -export \
	-inkey "$name.key" -in "$name.pem" \
	-passin "pass:" \
	-out "$name.pkcs12" \
	-passout "pass:" \
	-name "$name"
openssl x509 -text -noout -in "$name.pem" \
	-passin "pass:" > "$"

This creates a PEM file (public key), key file (private key), and pkcs12
file (certificate).

For Chrome and Firefox, use the respective configuration tool of the browser
to introduce this as a new personal certificate. For IE, it is sufficient to
import the certificate into the Windows certificate store.

For the server side of TLS, I usually use something of this sort:

    # SAN certificates are defined once on the http context level
    ssl_certificate    	   	/etc/nginx/certs/fullchain.pem;
    ssl_certificate_key		/etc/nginx/certs/private.pem;
    ssl_protocols		TLSv1.2 TLSv1.3;

    # TLSv1.3 requires openssl 1.1.1 or later. This tries to enable 0-RTT.
    #ssl_early_data		on;

    # Prefer the faster Diffie Hellman Parameters over slower RSA
    ssl_ciphers			"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers	on;
    ssl_ecdh_curve		secp384r1;
    # openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
    ssl_dhparam			/etc/nginx/certs/dhparam.pem;

    # SSL session parameters
    ssl_session_cache		shared:SSL:36m;
    ssl_session_timeout		3m;
    ssl_session_tickets		on;
    ssl_session_ticket_key	/etc/nginx/certs/ticket.key;
    ssl_session_ticket_key	/etc/nginx/certs/ticket.key.old;

with fullchain.pem being the public keys of the certificate chain, starting
with the most specific, i.e., the server's SAN certificate. I use a little
awk script to re-arrange public keys in the proper order - from the output
of a "openssl pkcs7 -print_certs ..."

and with dhparam.pem generated by 

openssl dhparam -out /etc/nginx/certs/dhparam.pem 4096

and ticket.key generated by 

openssl rand 80 > /etc/nginx/ticket.key

Next thing is that TLS can only be switched on per server, not per location.
I generally don't like SNI, so I prefer to have SAN certificates for

server {
        listen 443 ssl;
        server_name ...{all the names you need}...;
        ssl_trusted_certificate	/etc/nginx/certs/clients.pem;
        ssl_verify_client	optional_no_ca;
        ssl_verify_depth	1;


will do the trick, if clients.pem is simply a concatenation of all .pem
files you have produced earlier (the public keys of clients who are supposed
to have access). Note that I use the directive "ssl_trusted_certificate", so
the list of permitted CAs is not communicated, but rather the client has to
present a certificate. There is no requirement to use any specific CA. As
each certificate is self-signed, the "CA" (issuer) of each certificate is
the certificate itself.

You may now check in the nginx.conf or in a Javascript handler what the
client status is:

$ssl_client_verify will be NONE if no certificate was presented, SUCCESS if
the client specified one of the permitted certificates, and FAILED... if
there was some failure.

Once this is done, you can check $ssl_client_s_dn to find out who was
authenticated. You may use a map directive to map this variable to the
e-mail address from the certificate's DN, or look at any one of the other
attributes. What you can query you'll find here:

This should be a complete recipe on how to set up working self-signed client

If this works, you may play with other options :-)


Posted at Nginx Forum:,285456,285481#msg-285481

More information about the nginx mailing list