Certificates
Store your own TLS certificates, including wildcards, and see which certificate Ceryx serves for a domain.
Certificates stored through the API take priority over Let's Encrypt. A wildcard certificate stored for *.example.com serves subdomains such as app.example.com that have no certificate of their own. Responses never include private keys. See How certificates work for the full order in which Ceryx picks a certificate.
List stored certificates
GET/api/certificates
Responses
| Status | Description | Body |
|---|---|---|
200 |
Certificates stored through the API. | array of CertificateListItem |
Example
curl http://ceryx:8080/api/certificates
Responds with 200:
[
{
"domain": "*.example.com",
"meta": {
"vendor": "Example CA",
"san": [
"*.example.com"
],
"expires_at": "2027-02-01T00:00:00Z",
"issued_at": "2026-11-03T00:00:00Z",
"stored_at": 1793750400
}
}
]
Create or replace a certificate
POST/api/certificates
Stores the certificate for domain, replacing any existing one.
Request body
A CertificateCreate object.
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | Yes | Optional; must equal the domain in the URL. |
certificate |
string | Yes | PEM-encoded certificate. |
private_key |
string | Yes | PEM-encoded private key. |
chain |
string | No | PEM-encoded intermediate chain. |
meta |
CertificateMeta | No | Free-form metadata. Ceryx adds issued_at (when not given) and stored_at on every write. |
Responses
| Status | Description | Body |
|---|---|---|
201 |
The stored certificate (without the private key). | Certificate |
400 |
Missing or invalid input. | Error |
500 |
The storage backend failed, or a Ceryx environment variable the request needs has an unusable value (detail names it). |
Error |
Example
jq -n \
--rawfile certificate cert.pem \
--rawfile private_key key.pem \
--rawfile chain chain.pem \
'{domain: "*.example.com", certificate: $certificate, private_key: $private_key, chain: $chain,
meta: {vendor: "Example CA", san: ["*.example.com"], expires_at: "2027-02-01T00:00:00Z"}}' |
curl -X POST http://ceryx:8080/api/certificates \
-H 'Content-Type: application/json' \
-d @-
Responds with 201:
{
"domain": "*.example.com",
"has_certificate": true,
"has_chain": true,
"meta": {
"vendor": "Example CA",
"san": [
"*.example.com"
],
"expires_at": "2027-02-01T00:00:00Z",
"issued_at": "2026-11-03T00:00:00Z",
"stored_at": 1793750400
}
}
Get the certificate served for a domain
GET/api/certificates/{domain}
Returns the certificate Ceryx would serve for domain: an exact stored match, then a stored wildcard (*.parent), then certificate files from the route settings.
Path parameters
| Name | Type | Description |
|---|---|---|
domain |
string | Certificate domain; may be a wildcard such as *.example.com. |
Responses
| Status | Description | Body |
|---|---|---|
200 |
The certificate (without the private key). | Certificate |
404 |
No such resource. | Error |
Example
curl http://ceryx:8080/api/certificates/app.example.com
Responds with 200:
{
"domain": "app.example.com",
"has_certificate": true,
"has_chain": true,
"meta": {
"vendor": "Example CA",
"san": [
"*.example.com"
],
"expires_at": "2027-02-01T00:00:00Z",
"issued_at": "2026-11-03T00:00:00Z",
"stored_at": 1793750400
}
}
Update a stored certificate
PUT/api/certificates/{domain}
Updates the certificate stored for exactly domain (no wildcard or file fallback). certificate and private_key are replaced together; a replaced certificate takes the request's chain, or none. meta replaces the stored metadata when given.
Path parameters
| Name | Type | Description |
|---|---|---|
domain |
string | Certificate domain; may be a wildcard such as *.example.com. |
Request body
A CertificateUpdate object.
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | No | Optional; must equal the domain in the URL. |
certificate |
string | No | PEM-encoded certificate; requires private_key. |
private_key |
string | No | PEM-encoded private key; requires certificate. |
chain |
string | No | Used only with a new certificate; omitting it clears the chain. |
meta |
CertificateMeta | No | Free-form metadata. Ceryx adds issued_at (when not given) and stored_at on every write. |
certificate requires private_key.
private_key requires certificate.
Responses
| Status | Description | Body |
|---|---|---|
200 |
The updated certificate (without the private key). | Certificate |
400 |
Missing or invalid input. | Error |
404 |
No such resource. | Error |
500 |
The storage backend failed, or a Ceryx environment variable the request needs has an unusable value (detail names it). |
Error |
Example
curl -X PUT 'http://ceryx:8080/api/certificates/*.example.com' \
-H 'Content-Type: application/json' \
-d '{
"meta": {
"vendor": "Example CA",
"san": [
"*.example.com"
],
"expires_at": "2027-02-01T00:00:00Z",
"note": "Renewed by the platform team"
}
}'
Responds with 200:
{
"domain": "*.example.com",
"has_certificate": true,
"has_chain": true,
"meta": {
"vendor": "Example CA",
"san": [
"*.example.com"
],
"expires_at": "2027-02-01T00:00:00Z",
"note": "Renewed by the platform team",
"issued_at": "2026-11-10T09:30:00Z",
"stored_at": 1794303000
}
}
Delete a stored certificate
DELETE/api/certificates/{domain}
Idempotent; deleting a missing certificate also returns 204.
Path parameters
| Name | Type | Description |
|---|---|---|
domain |
string | Certificate domain; may be a wildcard such as *.example.com. |
Responses
| Status | Description | Body |
|---|---|---|
204 |
Deleted. | None |
500 |
The storage backend failed, or a Ceryx environment variable the request needs has an unusable value (detail names it). |
Error |
Example
curl -X DELETE 'http://ceryx:8080/api/certificates/*.example.com'
Responds with 204 and no body.