🌐 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

  1. 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" }
      }'
  1. Tu cliente crea un solo registro DNS: un CNAME al 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 el TXT _cloudwapp-verify que también viene en la respuesta.)
  2. 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.

EndpointDescripción
POST /api/v1/domainsRegistrar el hostname de un cliente
GET /api/v1/domains/mineListar tus hostnames, su estado y su tráfico
POST /api/v1/domains/mine/:id/verifyForzar verificación DNS
PATCH /api/v1/domains/mine/:idCambiar destino o headers inyectados
DELETE /api/v1/domains/mine/:idDesconectar 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:

HeaderQué trae
X-Forwarded-HostEl dominio que escribió el visitante (app.cliente.com) — con esto resuelves de qué cliente es
X-Forwarded-ProtoSiempre https: el TLS lo terminamos nosotros
X-Forwarded-ForIP real del visitante
X-CloudWapp-TenantTu id de usuario en CloudWapp (el dueño del hostname)
X-CloudWapp-Domain-IdId 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.