Debugging Nginx Client Certificate Verification: Fixing “No client certificate CA names sent” Error


17 views

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:

  1. Verify your CA certificate chain is complete
  2. Check file permissions (nginx typically runs as www-data or nginx user)
  3. Test with ssl_verify_client optional_no_ca temporarily
  4. 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;
}