Client Certificate Authentication Setup
Client certificate authentication (mutual TLS) requires both server and client to present valid certificates, providing strong two-factor authentication. This guide covers CA creation, client certificate generation, Nginx/Apache configuration, and CRL management.
Table of Contents
- Prerequisites
- Creating a Private CA
- Generating Client Certificates
- Nginx Client Certificate Setup
- Apache Client Certificate Setup
- Certificate Revocation Lists
- Testing Client Authentication
- Troubleshooting
- Automation
- Conclusion
Prerequisites
Before setting up client certificate authentication, ensure you have:
- OpenSSL installed
- Root or sudo access
- Web server (Nginx, Apache)
- Understanding of PKI concepts
- Test clients supporting client certificates
Creating a Private CA
Create a Certificate Authority for signing client certificates.
Create CA directory:
mkdir -p ~/ca/{certs,csr,newcerts,private}
cd ~/ca
chmod 700 private
touch index.txt
echo 1000 > serial
Generate CA private key:
openssl genrsa -aes256 -out private/ca.key 4096
Enter passphrase when prompted.
Create CA certificate (self-signed):
openssl req -config /etc/ssl/openssl.cnf \
-key private/ca.key \
-new -x509 -days 3650 \
-extensions v3_ca \
-out certs/ca.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=Client-CA"
Verify CA certificate:
openssl x509 -noout -text -in certs/ca.crt
Generating Client Certificates
Generate individual client certificates for authentication.
Create client certificate configuration:
cat > client.conf <<EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = v3_req
[req_distinguished_name]
C = US
ST = State
L = City
O = Organization
CN = client-user-1
[v3_req]
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
[client_cert]
basicConstraints = CA:FALSE
nsCertType = client
nsComment = "Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF
Generate client private key:
openssl genrsa -out private/client-user-1.key 2048
Create client certificate signing request:
openssl req -config client.conf \
-key private/client-user-1.key \
-new -out csr/client-user-1.csr
Sign client certificate with CA:
openssl x509 -req -in csr/client-user-1.csr \
-CA certs/ca.crt -CAkey private/ca.key \
-CAcreateserial -out certs/client-user-1.crt \
-days 730 -sha256 \
-extfile client.conf -extensions client_cert
Create PKCS12 keystore for browser/application import:
openssl pkcs12 -export \
-in certs/client-user-1.crt \
-inkey private/client-user-1.key \
-out certs/client-user-1.p12 \
-name "client-user-1" \
-passout pass:clientpassword
Verify client certificate:
openssl x509 -noout -text -in certs/client-user-1.crt
Nginx Client Certificate Setup
Configure Nginx to require client certificate authentication.
Nginx configuration:
server {
listen 443 ssl http2;
server_name api.example.com;
# Server certificate
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
# Client certificate verification
ssl_client_certificate /etc/ssl/certs/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
# Trusted client CA bundle
ssl_trusted_certificate /etc/ssl/certs/ca.crt;
# Session configuration
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
# Pass client certificate info to backend
location / {
# Client certificate as header
proxy_set_header SSL-Client-Cert $ssl_client_cert;
proxy_set_header SSL-Client-S-DN $ssl_client_s_dn;
proxy_set_header SSL-Client-Verify $ssl_client_verify;
proxy_pass http://backend:8080;
}
}
Optional client verification (allow requests without cert):
ssl_verify_client optional;
ssl_verify_client_depth 2;
location /public {
# Public endpoint, certificate not required
proxy_pass http://backend:8080;
}
location /private {
# Private endpoint, certificate required
if ($ssl_client_verify != SUCCESS) {
return 403 "Client certificate required";
}
proxy_pass http://backend:8080;
}
Test Nginx configuration:
sudo nginx -t
sudo systemctl reload nginx
Apache Client Certificate Setup
Configure Apache for client certificate authentication.
Apache VirtualHost configuration:
<VirtualHost *:443>
ServerName api.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
# Client certificate verification - REQUIRED
SSLVerifyClient require
SSLVerifyDepth 2
SSLCACertificatePath /etc/ssl/certs
SSLCACertificateFile /etc/ssl/certs/ca.crt
# Policy for client certificates
SSLRequire %{SSL_CLIENT_VERIFY} eq "SUCCESS"
# Pass client certificate to backend
<IfModule mod_headers.c>
RequestHeader set X-SSL-Client-Cert %{SSL_CLIENT_CERT}e
RequestHeader set X-SSL-Client-S-DN %{SSL_CLIENT_S_DN}e
RequestHeader set X-SSL-Client-Verify %{SSL_CLIENT_VERIFY}e
</IfModule>
# Proxy to backend
ProxyPreserveHost On
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</VirtualHost>
Optional client verification:
<VirtualHost *:443>
ServerName api.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
# Optional client certificate verification
SSLVerifyClient optional
SSLVerifyDepth 2
SSLCACertificateFile /etc/ssl/certs/ca.crt
<Location /public>
# No certificate required
ProxyPass http://localhost:8080/public
</Location>
<Location /private>
# Certificate required
SSLRequire %{SSL_CLIENT_VERIFY} eq "SUCCESS"
ProxyPass http://localhost:8080/private
</Location>
</VirtualHost>
Enable modules:
sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod headers
sudo systemctl reload apache2
Certificate Revocation Lists
Revoke compromised client certificates using CRL.
Initialize CRL database:
cd ~/ca
touch index.txt.attr
echo "unique_subject = no" > index.txt.attr
Revoke a client certificate:
openssl ca -config /etc/ssl/openssl.cnf \
-revoke certs/client-user-1.crt \
-keyfile private/ca.key \
-cert certs/ca.crt
Generate CRL (Certificate Revocation List):
openssl ca -config /etc/ssl/openssl.cnf \
-gencrl -out certs/ca.crl \
-keyfile private/ca.key \
-cert certs/ca.crt \
-crldays 30
View CRL contents:
openssl crl -in certs/ca.crl -text -noout
Deploy CRL to web servers:
sudo cp ~/ca/certs/ca.crl /etc/ssl/certs/
sudo chown root:root /etc/ssl/certs/ca.crl
sudo chmod 644 /etc/ssl/certs/ca.crl
Configure Nginx with CRL:
ssl_crl /etc/ssl/certs/ca.crl;
ssl_verify_client on;
Configure Apache with CRL:
SSLCARevocationFile /etc/ssl/certs/ca.crl
SSLCARevocationCheck chain
Automate CRL generation:
#!/bin/bash
# /usr/local/bin/update-crl.sh
cd ~/ca
openssl ca -config /etc/ssl/openssl.cnf \
-gencrl -out certs/ca.crl \
-keyfile private/ca.key \
-cert certs/ca.crt \
-crldays 30
# Deploy
sudo cp certs/ca.crl /etc/ssl/certs/
sudo systemctl reload nginx
sudo systemctl reload apache2
Add to crontab:
# Regenerate CRL weekly
0 2 * * 0 /usr/local/bin/update-crl.sh
Testing Client Authentication
Test client certificate authentication.
Test with curl:
# Without client certificate (should fail)
curl https://api.example.com
# Output: curl: (60) SSL: certificate problem
# With client certificate
curl --cert certs/client-user-1.crt \
--key private/client-user-1.key \
https://api.example.com
# With PKCS12 keystore
curl --cert certs/client-user-1.p12:clientpassword \
https://api.example.com
Test with openssl:
# Show client certificate in handshake
openssl s_client -connect api.example.com:443 \
-cert certs/client-user-1.crt \
-key private/client-user-1.key \
-showcerts
Test with Python:
import requests
from requests.auth import HTTPCertAuth
# Requests library
response = requests.get(
'https://api.example.com',
cert=('client.crt', 'client.key'),
verify='/path/to/ca.crt'
)
# Using PKCS12
import ssl
context = ssl.create_default_context()
context.load_cert_chain('client.p12', password='password')
import urllib.request
opener = urllib.request.build_opener(
urllib.request.HTTPSHandler(context=context)
)
response = opener.open('https://api.example.com')
Troubleshooting
Certificate verification failures:
# Check certificate chain
openssl verify -CAfile ~/ca/certs/ca.crt \
~/ca/certs/client-user-1.crt
# Verify certificate against CA
openssl x509 -in ~/ca/certs/client-user-1.crt \
-issuer -noout
Common issues:
# Certificate expired
openssl x509 -in client.crt -noout -dates
# Wrong CA configured
openssl verify -CAfile /etc/ssl/certs/ca.crt client.crt
# Certificate revoked
openssl verify -CAfile ca.crt -CRLfile ca.crl client.crt
Nginx debugging:
# Enable SSL debug
error_log /var/log/nginx/error.log debug;
# Check for certificate validation errors
sudo tail -f /var/log/nginx/error.log
Apache debugging:
# Enable SSL debug
LogLevel ssl:debug
# Check for certificate validation errors
sudo tail -f /var/log/apache2/error.log | grep SSL
Automation
Automate client certificate generation and deployment.
Create certificate generation script:
#!/bin/bash
# /usr/local/bin/generate-client-cert.sh
USERNAME=$1
if [ -z "$USERNAME" ]; then
echo "Usage: $0 <username>"
exit 1
fi
cd ~/ca
# Generate key
openssl genrsa -out private/${USERNAME}.key 2048
# Create CSR
openssl req -config client.conf \
-key private/${USERNAME}.key \
-new -out csr/${USERNAME}.csr \
-subj "/C=US/ST=State/L=City/O=Organization/CN=${USERNAME}"
# Sign certificate
openssl x509 -req -in csr/${USERNAME}.csr \
-CA certs/ca.crt -CAkey private/ca.key \
-CAcreateserial -out certs/${USERNAME}.crt \
-days 730 -sha256 \
-extfile client.conf -extensions client_cert
# Create PKCS12
openssl pkcs12 -export \
-in certs/${USERNAME}.crt \
-inkey private/${USERNAME}.key \
-out certs/${USERNAME}.p12 \
-name "${USERNAME}" \
-passout pass:password123
echo "Certificate generated: certs/${USERNAME}.p12"
Make executable and use:
chmod +x /usr/local/bin/generate-client-cert.sh
/usr/local/bin/generate-client-cert.sh user-123
Conclusion
Client certificate authentication provides strong mutual authentication without password-related vulnerabilities. This guide covered CA creation, client certificate generation, Nginx/Apache configuration, CRL management, and testing. For production deployments, implement proper key management, rotate certificates regularly, maintain CRLs, monitor certificate expiration, and automate certificate generation and revocation. Client certificate authentication excels for securing APIs, microservices, and internal communication channels requiring strong authentication.


