When implementing mutual TLS (mTLS) with Nginx, the certificate verification process involves several critical steps:
Client Hello → Server Hello →
Certificate Request (with CA names) →
Client Certificate →
Verification →
Secure Connection Established
The key error message "No client certificate CA names sent" indicates Nginx isn't properly advertising which Certificate Authorities (CAs) it accepts for client authentication.
Your current configuration appears mostly correct, but let's enhance it with additional debugging parameters:
server {
listen 443 ssl;
server_name my-server;
# Standard SSL config
ssl_certificate /etc/nginx/ssl/my-server.crt;
ssl_certificate_key /etc/nginx/ssl/my-server.key;
# Client verification essentials
ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_verify_client on; # Change from 'optional' to enforce validation
# Debugging parameters
ssl_verify_depth 3;
error_log /var/log/nginx/ssl_error.log debug;
# Important security headers
add_header X-Client-Verify $ssl_client_verify;
add_header X-Client-DN $ssl_client_s_dn;
add_header X-Client-Cert $ssl_client_cert;
}
The client CA file must contain all intermediate certificates in PEM format. Verify with:
openssl crl2pkcs7 -nocrl -certfile /etc/nginx/ssl/client-ca.crt | \
openssl pkcs7 -print_certs -text -noout
Example of proper CA chain format:
-----BEGIN CERTIFICATE-----
(Intermediate CA 1)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(Root CA)
-----END CERTIFICATE-----
For comprehensive testing, use these OpenSSL commands:
# Basic connection test
openssl s_client -connect my-server:443 -showcerts
# Full client certificate test
openssl s_client -connect my-server:443 \
-cert client.crt -key client.key \
-CAfile /etc/nginx/ssl/client-ca.crt \
-status -tlsextdebug
When verification fails, check these diagnostic points:
- Confirm Nginx has read access to all certificate files
- Verify certificate paths in configuration
- Check SELinux/apparmor permissions if enabled
- Test with
ssl_verify_client optional_no_ca
as interim debug - Inspect
ssl_error.log
for detailed handshake errors
Here's a verified configuration that works with client certificates:
http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name secure.example.com;
# Server certificates
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-chain.crt;
ssl_verify_client on;
ssl_verify_depth 2;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:...';
location / {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
proxy_pass http://backend;
}
}
}
Issue | Solution |
---|---|
No CA names sent | Verify ssl_client_certificate path and file permissions |
Validation fails silently | Enable debug logging and check error_log |
Incorrect chain | Rebuild CA bundle with proper certificate order |
Certificate expired | Check validity periods with openssl x509 -dates -noout |
Remember to test with different verification levels (on
, optional
, optional_no_ca
) during troubleshooting to isolate the issue.
The error message "No client certificate CA names sent" typically indicates that while Nginx is configured for client certificate verification, it's not properly advertising the acceptable Certificate Authorities to clients during the SSL handshake. This prevents the mutual authentication process from completing successfully.
From your configuration, I notice several key elements that need verification:
ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_verify_client optional;
The critical missing piece is the ssl_trusted_certificate
directive. Unlike ssl_client_certificate
, this directive tells Nginx which CAs to advertise to clients during the handshake.
Here's the corrected server block configuration:
server {
listen my.addr.here:443 ssl;
server_name my-server;
ssl on;
ssl_certificate /etc/nginx/ssl/my-server.crt;
ssl_certificate_key /etc/nginx/ssl/my-server.key;
ssl_dhparam /etc/nginx/ssl/my-server.dhparam;
ssl_protocols TLSv1.1 TLSv1.2;
# Updated SSL configuration
ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_trusted_certificate /etc/nginx/ssl/client-ca.crt; # This is critical
ssl_verify_client on; # Changed from optional to enforce verification
# Enhanced logging for debugging
error_log /var/log/nginx/client_cert_error.log debug;
# ... rest of your configuration ...
}
After making these changes, test with OpenSSL using verbose output:
openssl s_client -connect my-server:443 \
-cert client.crt -key client.key \
-CAfile /etc/nginx/ssl/client-ca.crt \
-status -tlsextdebug -msg
You should now see the CA names being advertised in the handshake process.
If issues persist, consider these diagnostic steps:
- Verify your CA certificate chain is complete
- Check file permissions (nginx typically runs as www-data or nginx user)
- Test with
ssl_verify_client optional_no_ca
temporarily - Inspect Nginx debug logs for SSL negotiation details
Here's how we implemented this in a production environment:
# In nginx.conf
http {
ssl_client_certificate /etc/ssl/certs/trusted-clients.crt;
ssl_trusted_certificate /etc/ssl/certs/trusted-clients.crt;
ssl_verify_depth 3;
# Map client certificates to user IDs
map $ssl_client_s_dn $ssl_client_dn {
default "";
~/CN=(?[^/]+) $CN;
}
}
# In server block
server {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
# Use client cert CN as username
add_header X-Authenticated-User $ssl_client_dn;
}