Custom customer domains
Let a hosted app serve its own customers on their domains — status.client.com, shop.client.ie — with automatic HTTPS, without anyone adding those domains in SpipCP. One toggle, one CNAME per customer, certificates issued on demand.
Some apps serve their customers on the customer's own domain: a status page on
status.client-one.com, a storefront on shop.client-two.ie. Those hostnames belong to your app's
customers — you can't add each of them in SpipCP, and you shouldn't have to. Custom customer
domains turns that into a single toggle: your app flips it on, and its end-customers point their own
(sub)domains at it. This node's Caddy obtains and renews each certificate lazily at the first HTTPS
request, and your app decides which hostnames are allowed via an ask endpoint it owns.
How this differs from Domains & SSL
Domains & SSL is for domains you attach — the app's own hostnames, one
site_domains row each, verified and issued through the panel. This page is for domains your
app's customers bring: nobody adds them in SpipCP, there's no row per hostname, and the panel only
observes the certificates Caddy issues. Zero panel writes per customer domain.
Enable it
On the site's workspace, open Networking → Domains and find the Custom customer domains card.
- Site type — only node and docker sites qualify (types that answer HTTP directly). The card tells you when a site type can't use it.
- One per node — a node holds one on-demand app at a time. If another site on the same node already offers custom customer domains, the card says so — put a second app on a second node.
- Ask endpoint path — the path this node calls to authorize each hostname (default
/api/v1/caddy/ask). See the ask contract for what your app must implement.
Give customers a CNAME target
Attach the app's own stable hostname the normal way first (the Attach a domain card). The Custom customer domains card then lists your attached hostnames as CNAME targets. Tell each customer to create one record:
A CNAME:
status.client-one.com→ your app's stable hostname (e.g.app.yourdomain.com).
Apex domains (no subdomain) can't CNAME — the customer uses an A/AAAA record to the node, or an ALIAS where their provider supports it.
Where the customer's CNAME lives — automated, or by hand
The customer-facing step is always one record. Which of three tiers it lands in is detected, not picked — the same Hosted / Connected / External authority the fleet-wide Domains inventory uses:
| Tier | Where the zone lives | Automated? |
|---|---|---|
| Hosted | Your own self-hosted PowerDNS | Yes, once allow-listed (below) |
| Connected | A connected provider account you hold | Yes, once allow-listed (below) |
| External | The customer's own DNS (the common case), or a zone neither tier covers | No — you get exact records to relay |
For a Hosted or Connected zone, the Custom hostnames section below can create the record for you — via your app's API token or the operator's "Add the record for me" button. For everything else, the customer (or you, on their behalf) still creates the record by hand at whoever holds the DNS.
- Cloudflare orange-cloud. It works — the ACME HTTP-01 challenge passes through Cloudflare on port
80, and after the first issuance "Full (strict)" is satisfied by the certificate on your node. Two
caveats: the very first proxied request can return a
525until issuance completes, and grey-cloud (DNS-only) is the smoother, recommended posture. Custom hostnames never proxies a record it creates — ACME HTTP-01 needs a direct path to the origin.
Custom hostnames — automate the DNS step
The Custom hostnames section (below the Custom customer domains card) gives your app a machine seam for the DNS step, plus an operator door for doing it by hand. It's deny-by-default: nothing writes into a zone the operator hasn't explicitly allowed.
The automated-zones allow-list
A hostname whose zone SpipCP fully controls (Hosted or Connected) still only gets a written
record if that zone is on this list. Add a zone apex (e.g. client1.com), or "*" for every zone
this installation controls — a loud confirmation warns you before "*" takes effect, since it means
your app's token can write into any zone you run, including other customers' zones on your
self-hosted nameservers. Most installs list specific zones.
A zone left off the list isn't blocked — a registration against it just comes back as instructions (the exact records to relay), same as an External hostname. Widen the allow-list whenever you see real demand for it in the registered-hostnames table below.
Minting the app token
Mint app token creates a spat_… bearer token bound to this one site, scoped to
custom-hostnames permissions only — it can never reach another site or a fleet-wide surface. Shown
once; paste it into your app's environment. See the Custom hostnames API
for what your app calls with it.
The registered-hostnames table
Every hostname registered for this site — by your app's token, or by the operator below — with its
tier, state (pending / active / instructions / failed / removing / removal-manual), and
source (App / Operator). The kebab (⋮) menu removes a hostname.
Add the record for me (the operator door)
Register a customer's hostname yourself, from the card — the same engine your app's token calls. Type the hostname and click Register: a Hosted/Connected + allow-listed hostname plans a real record and provisions it; anything else renders the exact records to relay to the customer.
Watch issued certificates
Once a customer's domain resolves and someone hits it over HTTPS, Caddy issues the certificate and it appears in the Issued certificates table on the card — hostname, issuer, and expiry.
This list is read-only: the panel observes the certificates (it reads them from the node's cert facts) but never provisions or renews per hostname — renewal is automatic, handled by Caddy. Use Refresh to re-read the node's facts. The same roll-up appears fleet-wide on Networking → SSL in the On-demand custom domains section.
Troubleshooting
- The HTTPS handshake fails for a customer. Either your app's ask endpoint denied the hostname (it's not a verified customer domain in your app) or the app is down. The ask endpoint is the only issuance gate — if it says no, no certificate is issued.
- The ask endpoint is down. No new certificates issue while it's unreachable — but certificates already issued keep serving. That's the correct failure direction.
- Disable ≠ revoke. Turning the feature off removes the catch-all so no new hostnames are served, but certificates already in Caddy's store remain (unused) until they expire naturally.
Next steps
- Custom hostnames API — the four calls your app drives with its site-bound token, and the register → poll → prime → poll recipe.
- The ask contract — what your app implements to authorize each certificate issuance.
- Domains: the fleet inventory — the Hosted/Connected/External tiers, and where registered custom hostnames show up fleet-wide.
Domains & SSL
Give a site a real hostname with automatic HTTPS — point one DNS record at it, and SpipCP handles the certificate, renewal, and a check that it actually serves.
Python sites
Run a Python app (WSGI via gunicorn, or ASGI via uvicorn) as a self-healing systemd service — pick the server at launch, tune it later, and let SpipCP install the runtime on the way.



