Client Certificate Authentication — Debug Guide

Quick checks

Verify nginx is enforcing client certs

# Should fail with 400 or 403 (no cert)
curl -k -X GET https://ldap.cicd.example.com/ -v 2>&1 | grep "< HTTP"

# Should succeed (with cert)
curl -k --cert user.crt --key user.key https://ldap.cicd.example.com/ -v 2>&1 | grep "< HTTP"

Check nginx error log for SSL errors

tail -f /var/log/nginx/error.log

Note: ssl_verify_client on rejections do NOT appear in the error log. Only optional mode logs verification results.

Check access log for status codes

tail -f /var/log/nginx/access.log

Look for 400 (TLS-level rejection) vs 403 (HTTP-level rejection) vs 419/500 (application error).

Identifying which server block handles a request

Add a debug header inside the location / block:

add_header X-Debug-Server "block-name" always;

Check the response headers in browser DevTools → Network tab. If the header is missing, the request was rejected before reaching the location block.

Testing client cert from the command line

Basic connectivity with cert

curl -k --cert user.crt --key user.key \
  https://ldap.cicd.example.com/ -v

POST request simulating AJAX

curl -k --cert user.crt --key user.key \
  -X POST https://ldap.cicd.example.com/ajax/bases \
  -H "X-CSRF-TOKEN: test" \
  -H "Cookie: phpldapadmin_session=test" \
  -v

Check which cert the server expects

openssl s_client -connect ldap.cicd.example.com:443 2>&1 | grep -A5 "Acceptable client certificate"

Browser-specific debugging

Firefox

  1. Open DevTools (F12) → Network tab
  2. Reload the page — check if Firefox prompts for client cert
  3. Look at the failing AJAX request:
  4. Status 400 + 237 bytes = nginx TLS rejection (cert not sent)
  5. Status 403 = nginx HTTP rejection (cert sent but invalid, or cookie missing)
  6. Status 419 = Laravel CSRF mismatch
  7. Status 500 = Application error

Verify cert is installed in Firefox

Settings → Privacy & Security → Certificates → View Certificates → Your Certificates

Force Firefox to re-prompt for cert

Close all tabs to the domain, then clear the SSL state: - Settings → Privacy & Security → Certificates → View Certificates → delete the cert decision - Or restart Firefox entirely

In browser DevTools → Network tab, click the initial GET request → Response Headers. Look for:

Set-Cookie: client_cert_verified=1; Path=/; Secure; HttpOnly; SameSite=Strict

In browser DevTools → Network tab, click the AJAX POST request → Request Headers. Look for:

Cookie: client_cert_verified=1; ...

Check nginx variables

Add to the nginx log format temporarily:

log_format debug '$remote_addr - $ssl_client_verify - $cookie_client_cert_verified - $request';
access_log /var/log/nginx/debug.log debug;

This logs the cert verification status and cookie value for each request.

Common scenarios

Symptom Cause Fix
400 on all requests Client cert not installed or wrong CA Check cert installation in browser
400 only on AJAX Browser not sending cert on XHR connections Apply session cookie workaround
403 on all requests Cert sent but verification fails Check CA cert path in nginx, check cert expiry
Page loads, AJAX 400, no error log ssl_verify_client on TLS rejection Switch to optional + cookie workaround
Cookie not being set Initial page load also failing Check cert is valid and CA matches