Skip to main content

Database Schema

Certeasy's schema is managed automatically via embedded migrations — you never write DDL yourself. This page is a functional map of the core ACME tables and how they change as an order progresses; it is not an exhaustive column-level reference. Internal/support tables (job queue, licensing, PKI request tracking, audit state) are managed automatically and are not covered here.

ACME Flow Overview

StepEndpointServer ActionTables Affected
New AccountPOST /acme/newAccountCreates a client accountacme_accounts
New OrderPOST /acme/newOrderCreates an order with authorizations and challengesacme_orders, acme_authorizations, acme_challenges
Challenge ValidationPOST /acme/chall/<id>Client responds, server validates asynchronouslyacme_challenges, acme_authorizations
FinalizationPOST /acme/finalize/<id>Client sends CSR → certificate issuedacme_orders, acme_certificates
Certificate RetrievalGET /acme/cert/<id>Client downloads certificateRead only
RevocationPOST /acme/revoke-certRevokes a certificateacme_certificates
Replay ProtectionAutomaticAnti-replay noncesacme_nonces
AuditingAutomaticAll significant actionsTamper-evident audit log (JSONL, separate from the database)

Table: acme_accounts

Stores registered ACME client accounts (RFC 8555 §7.1.2).

ColumnDescription
idAccount ID (e.g. /acme/acct/123)
jwkClient public key in canonical JSON
contactList of mailto: addresses
statusvalid, deactivated, revoked
tos_agreed_atTimestamp of Terms of Service acceptance

Created: POST /acme/newAccount (when JWK thumbprint is new)

State transitions:

  • validdeactivated: via POST /acme/acct/{id} with "status":"deactivated"
  • validrevoked: on full account revocation

Table: acme_nonces

Anti-replay nonces used in JWS request headers.

ColumnDescription
nonceRandom nonce value
issued_atWhen the nonce was issued
used_atWhen the nonce was consumed (null if unused)

Every ACME response generates a new nonce. Once a nonce is used in a valid JWS request, it is marked used and cannot be reused. Expired nonces are cleaned up periodically.


Table: acme_orders

Represents a certificate order.

ColumnDescription
idOrder identifier
account_idOwning account
statuspending, ready, processing, valid, invalid
csrRaw CSR bytes / DER (set at finalize)
not_before, not_afterRequested validity window
expires_atOrder expiry
certificate_idLinked certificate (set when issued)

Created: POST /acme/newOrder

Status flow:

pending → ready (all authorizations valid)
→ processing (finalize received)
→ valid (certificate issued)
→ invalid (challenge or issuance failure)

Table: acme_authorizations

Proof of control for each identifier in an order.

ColumnDescription
idAuthorization ID (appears in its public URL)
identifier_valueDNS name (e.g. app.corp.internal)
statuspending, valid, invalid
expires_atAuthorization expiry
wildcardWhether this is a wildcard authorization
error_msgValidation error detail (if failed)

When at least one challenge for an authorization becomes valid, the authorization becomes valid. When all authorizations for an order are valid, the order status moves to ready.


Table: acme_challenges

Validation challenges (DNS-01, HTTP-01, TLS-ALPN-01) for each authorization.

ColumnDescription
idChallenge identifier
challenge_typedns-01, http-01, tls-alpn-01
statuspending, processing, valid, invalid
tokenChallenge token
key_authorizationComputed key authorization
validated_atTimestamp of successful validation

Created: automatically with newOrder

Updated: on POST /acme/challenge/<id> → moves to processing, then valid or invalid


Table: acme_certificates

Issued TLS certificates.

ColumnDescription
idCertificate identifier
account_idOwning account
order_idOriginating order
pem_chainPEM certificate chain (never includes private key)
not_before, not_afterValidity window
fingerprintSHA-256 fingerprint of the leaf certificate
revoked_atRevocation timestamp (null if active)
revoke_reasonRFC 5280 reason code (0–10, excluding 7)

Created: POST /acme/finalize/<id> after successful ADCS issuance

Updated: POST /acme/revoke-cert


Full Flow Reference

POST /acme/newAccount
→ INSERT acme_accounts

POST /acme/newOrder
→ INSERT acme_orders
→ INSERT acme_authorizations (one per identifier)
→ INSERT acme_challenges (one per auth × challenge type)

POST /acme/challenge/{id}
→ UPDATE acme_challenges (status → processing)
[async job validates DNS/HTTP/TLS]
→ UPDATE acme_challenges (status → valid/invalid)
→ UPDATE acme_authorizations (status → valid if one challenge valid)
→ UPDATE acme_orders (status → ready if all authorizations valid)

POST /acme/finalize/{id}
→ UPDATE acme_orders (status → processing)
[async job submits CSR to ADCS]
→ INSERT acme_certificates
→ UPDATE acme_orders (status → valid, certificate_id → ...)

GET /acme/cert/{id}
→ SELECT acme_certificates

POST /acme/revoke-cert
→ UPDATE acme_certificates (revoked_at, revoke_reason)