🌐 documentación
Dominios custom con SSL automático
Dale a cada cliente de tu SaaS su propia dirección con HTTPS sin tocar certificados. Es lo mismo que vende Cloudflare for SaaS, pero incluido en tu plan y apuntando al backend que tú quieras (esté o no en CloudWapp).
Las dos formas de dárselo a un cliente
Terminan igual y se configuran igual. Lo que cambia es de quién es el dominio y, por lo tanto, quién toca el DNS.
Un subdominio tuyo — acme.tusaas.com. El dominio es tuyo, así que tu cliente no
hace nada. Es lo típico al dar de alta una cuenta. Crea una sola vez un CNAME
comodín *.tusaas.com apuntando al anchor y desde ahí cada cliente nuevo funciona sin
volver a tocar el DNS: te basta con registrar el hostname por API cuando lo das de alta.
El dominio propio de tu cliente — app.acme.com. Tu cliente compra su dominio
(donde quiera, o desde CloudWapp) y apunta un CNAME al anchor. Verificamos y emitimos
el certificado en su primera visita. Es lo que hace que tu producto se vea como suyo
—su marca, su dominio, su candado— y suele ser el motivo para cobrar un plan superior.
Cómo se conecta
- Registrás el hostname de tu cliente y su destino:
curl -X POST https://tudominio.com/api/v1/domains \
-H "x-api-key: TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domainName": "app.cliente.com",
"targetUrl": "https://mi-saas.com/tenants/cliente",
"injectHeaders": { "X-Tenant-Id": "cliente-42" }
}'
- Tu cliente crea un solo registro DNS: un
CNAMEal anchor que te devuelve la API. Con eso queda verificado — apuntar el tráfico ya prueba que controla el dominio. (Si prefiere verificar antes de mover el tráfico, o su dominio es un apex sin CNAME, puede usar elTXT _cloudwapp-verifyque también viene en la respuesta.) - En la primera visita, Caddy consulta a la API si el dominio está autorizado y emite el certificado SSL en segundos (TLS on-demand). Las renovaciones son automáticas.
El proxy reenvía a tu backend con headers de tenant (X-CloudWapp-Tenant,
X-Forwarded-Host) más los injectHeaders que definas, así una sola instancia
atiende a todos tus clientes white-label. En el panel ves el estado de cada
hostname y cuánto tráfico sirvió este mes.
| Endpoint | Descripción |
|---|---|
POST /api/v1/domains | Registrar el hostname de un cliente |
GET /api/v1/domains/mine | Listar tus hostnames, su estado y su tráfico |
POST /api/v1/domains/mine/:id/verify | Forzar verificación DNS |
PATCH /api/v1/domains/mine/:id | Cambiar destino o headers inyectados |
DELETE /api/v1/domains/mine/:id | Desconectar el hostname |
Cada plan incluye una cantidad de dominios de clientes (5 en Explorer, 25 en Starter, 100 en Hobbyist); los dominios propios conectados a tus apps del PaaS tienen su propio cupo aparte.
Cómo lo recibe tu backend
Cada request reenviada llega con estos headers:
| Header | Qué trae |
|---|---|
X-Forwarded-Host | El dominio que escribió el visitante (app.cliente.com) — con esto resuelves de qué cliente es |
X-Forwarded-Proto | Siempre https: el TLS lo terminamos nosotros |
X-Forwarded-For | IP real del visitante |
X-CloudWapp-Tenant | Tu id de usuario en CloudWapp (el dueño del hostname) |
X-CloudWapp-Domain-Id | Id del hostname, por si prefieres una clave estable al dominio |
Más los injectHeaders que definas al conectar el dominio — por ejemplo
X-Tenant-Id con el id que ya usas en tu base de datos, y te ahorras el lookup.
El path se concatena: si el destino es https://mi-saas.com/tenants/acme y el
visitante entra a app.acme.com/precios, te llega a /tenants/acme/precios con su
query string intacta.
// Next.js — app/page.tsx
import { headers } from 'next/headers';
export default async function Page() {
const h = await headers();
const tenant = h.get('x-tenant-id') ?? h.get('x-forwarded-host');
const data = await getTenantData(tenant);
return <h1>Bienvenido a {data.name}</h1>;
}
// Astro — src/middleware.ts (output: 'server')
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (ctx, next) => {
const host = ctx.request.headers.get('x-forwarded-host') ?? ctx.url.host;
ctx.locals.tenant = await getTenantByDomain(
ctx.request.headers.get('x-tenant-id') ?? host,
);
if (!ctx.locals.tenant) return new Response('Dominio no configurado', { status: 404 });
return next();
});
// Express — un middleware y ya tienes multi-tenant
app.use(async (req, res, next) => {
const host = req.get('x-forwarded-host') || req.hostname;
req.tenant = await db.tenant.findFirst({
where: { domain: req.get('x-tenant-id') || host },
});
if (!req.tenant) return res.status(404).send('Dominio no configurado');
next();
});
Comprar dominios (registrar integrado)
También podés comprar dominios sin salir de CloudWapp (integración Namecheap):
cloudwapp domains check midominio.com # disponibilidad y precio
cloudwapp domains register midominio.com # compra + DNS apuntado a tu server
Los subdominios de tus apps (mi-app.apps.tudominio.com) se enrutan solos en cada
deploy — no hay que configurar nada.