When a client authenticates with tls_client_auth, edge acts as that client, and auth recognises it by an X.509 certificate presented in the TLS handshake. This page explains the three ways that certificate reaches edge, how it is renewed and what breaks. Read it if you run native apps or an edge-fronted web client with certificate authentication. You need central and auth configured with a mutual-TLS listener (Sender-constrained tokens).
The three modes
You choose the mode per client when you register it. The console wizard chooses for you: a Mobile or desktop app is registered with enrollEdgeClientCertificate, a Web app with issueEdgeClientCertificate.
| Issue | Enroll | cert-sync | |
|---|---|---|---|
| Client field | issueEdgeClientCertificate: true | enrollEdgeClientCertificate: true | edgeClientCertificate supplied by you |
| Who generates the key | central | each edge replica, in memory | whatever issues the certificate |
| Where the key is stored | In central, encrypted, and synced to edge encrypted for its RSA key | Only in the process of the edge replica | In central, as in the previous mode |
| Certificate per replica | One, shared | One per replica, same subject | One, shared |
| Who renews | central, renew-before before expiry | edge, with a third of the lifetime left | Whatever issues it, and cert-sync copies the new one |
| Client types | Web or native | Native only (applicationType: native) | Web or native |
| Status | Kept for existing clients | Preferred for new native clients | For a CA you operate yourself |
The first two are mutually exclusive with each other and with a supplied certificate. Why enroll is native only: a web client’s tokens are bound to its certificate (RFC 8705), so every replica must present the same one, and replicas that each enroll for their own certificate would not. A native app’s tokens are bound to the device DPoP key, so any replica’s certificate works.
All three need the CA behind the certificate to be trusted by auth: it must be in the mutual-tls.trusted-certificates file of auth.
Enrollment, step by step
- You register the client with
enrollEdgeClientCertificate: true,authMethod: tls_client_auth,applicationType: nativeand, optionally,mtlsAuth. WithoutmtlsAuththe client is recognised by the subjectCN=<client>,OU=<tenant>,O=Versola.centralstores only the fact that the client is enrolled. edgeloads clients fromcentraleveryconfiguration-cache-refresh-interval. For an enrolled client the record carries the subject the certificate must name, and no certificate.- The replica generates an EC P-256 key pair in memory and a PKCS#10 request for exactly that subject.
- It sends the request to
POST {central.url}/configuration/clients/edge-certificate/sign, authenticated with the signed token thatedgeuses for all sync calls. Only a registerededgecan call this endpoint, and it is not part of the admin API. centralchecks that the client is enrolled, that this edge serves the client’s tenant, and that the request names exactly the registered subject and alternative names. It asks the CA to sign (the lifetime isclient-certificates.validity), checks that the returned certificate carries whatmtlsAuthexpects, records the serial, time and edge in the tableedge_certificate_enrollment, and returns the chain.edgewrites the certificate and key to files, because the TLS library takes file paths, and presents them on calls toauth.
Rotation is part of the same loop. On each client load, a certificate with a third of its lifetime or less left is replaced; for the default 14 days that is about 4 days 16 hours. If the CA is unreachable during the window, edge keeps the current certificate until it expires and logs edge_certificate_renewal_failed. A restart enrolls again, which costs one signing per client.
Timings
| Setting | Where | Default |
|---|---|---|
| Certificate validity | central, client-certificates.validity | 14 days |
| Renew an issued certificate when less than this remains | central, client-certificates.renew-before | 4 days |
How often central checks issued certificates | central, client-certificates.check-interval | 1 hour |
| Renew an enrolled certificate | edge, fixed | at one third of the lifetime left |
When edge notices a change | edge, configuration-cache-refresh-interval | generated: 5 minutes |
central renews only certificates it issued. If you supply a certificate through a client update, central stops managing that client’s certificate. POST /configuration/clients/edge-certificate/renew?clientId=<id> renews one issued certificate immediately.
Where files live
edge never keeps the key or certificate in the database. To present them it writes them under a temporary directory, versola-client-certificates* in the system temp directory, created with mode 700. Each file is created with mode 600 and named after the SHA-256 of its content, so a renewed certificate lands next to the old one. The directory is removed on graceful shutdown. A crash leaves it behind: keep the temp directory off shared or persistent volumes, and restrict who can read the container filesystem.
Central CA backends
central does not hold a CA key. It delegates signing to the backend named in the client-certificates block of its configuration. Exactly one of step-ca and cert-manager is set.
client-certificates {
validity = "14 days"
renew-before = "4 days"
step-ca {
url = "https://step-ca.example.com:9000"
root-certificate = "/app/ca/root_ca.crt"
provisioner = "central"
provisioner-key = "/app/ca/provisioner.json"
}
}
| Backend | Used by | How it signs |
|---|---|---|
step-ca | Compose targets (local Docker, vps) | One-time token from a JWK provisioner named central, with a 14-day maximum and a template that keeps the request subject and limits the certificate to client authentication. The provisioner key is the only secret central holds |
cert-manager | Kubernetes | A CertificateRequest created with the pod’s service account, which may only create, get and delete those in its namespace. Keys: issuer-name, plus issuer-kind (default ClusterIssuer), issuer-group (default cert-manager.io) and namespace (default: the pod’s) |
client-certificate-authority | Local development only | central holds the CA certificate and its key and signs itself. Used when no client-certificates block names a CA. Avoid it elsewhere |
A step-ca created before the subject-keeping template existed rewrites the subject to the bare CN; central then refuses a longer subject_dn registration and says why. Without any CA, central refuses a registration that asks for an issued certificate.
cert-sync
For a CA you run yourself, or cert-manager issuing through the chart’s pki.clients, the cert-sync command in the versola-tools image copies certificates into central. It reads $CERT_SYNC_DIR/<client-id>/tls.crt and tls.key (unencrypted PKCS#8) and sends PUT /configuration/clients with only edgeClientCertificate whenever the content changes.
| Variable | Meaning |
|---|---|
CENTRAL_URL | Central API base URL |
CENTRAL_SECRET, CENTRAL_SECRET_FILE, CENTRAL_RESOURCE_SECRET | The central resource secret used for Basic authentication |
CERT_SYNC_INTERVAL_SECONDS | Seconds between passes. 0 runs one pass and exits |
It is off by default.
What to monitor
edgelogs. The request log line carries anerrorkey.edge_certificate_unavailablemeans a client was dropped because no certificate could be obtained;edge_certificate_renewal_failedmeans a renewal failed and the old certificate is still in use, with its expiry in the description.centrallogs. Each signing logssigned certificate <serial> for client '<id>' at edge '<edge>', valid until <time>. Each issuance logs the serial and expiry too. Alert on a client whose last signing is older than two thirds of the validity.- Tables.
edge_certificate_enrollment(enrolled clients, last signing) andclient_certificate_issuance(issued certificates: serial and expiry, no key material) are the audit trail. - Symptoms.
/native/*answering404for a client that exists, or401 invalid_clientfromauth, usually meansedgeholds no valid certificate for it.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
/native/* returns 404 for the client | edge has no certificate for it: enrollment failed, the client is not in this edge’s tenants, or no native block | Check the edge log for edge_certificate_unavailable and the answer of central |
central answers the signing request with a refusal | The client is not enrolled, is not served by this edge, or the request does not name the registered subject | Check the registration. Retrying does not help |
central refuses the signed result | The CA’s certificate lacks the subject type and value from mtlsAuth (for example a CA template that rewrites the subject) | Fix the CA template or the mtlsAuth value |
| Registration refused: no CA | issueEdgeClientCertificate without a client-certificates or client-certificate-authority block | Configure a backend |
auth returns invalid_client | The issuing CA is not in auth’s mutual-tls.trusted-certificates | Add the CA to that file and restart auth. During a CA change keep both CAs in the file |
| Calls fail after the CA expired | The CA certificate itself ran out, and issuance reports it | Replace the CA |
edge cannot reach auth’s listener | Pin mismatch: native.trusted-certificates does not match the leaf certificate auth presents | Pinned certificates |
Next steps
- Native apps: the flow that uses this certificate.
- Edge configuration: the
nativeblock and pinned certificates. - How to build a native mobile app flow: create the client and walk the calls.