Freestyle Docs

Freestyle / Docs

Wildcard Domains

Serve every subdomain of a domain from Freestyle VMs with a wildcard certificate and DNS record.

A wildcard domain serves any subdomain of a domain you own — alice.example.com, bob.example.com, and anything else — without setting up each one in advance. It takes three pieces: a wildcard DNS record, a wildcard TLS certificate, and TLS ingress rules routing the hostnames — either one wildcard rule sending every subdomain to the same place, or a rule per hostname when different subdomains land on different VMs.

Wildcards need a domain of your own. A free style.dev name is a single hostname, and a wildcard rule over one is refused.

Delegate The Challenge Zone

All issuance is proven over DNS, so Freestyle has to be able to publish a challenge record under your domain. If you already set this up for an ordinary domain it is the same record and there is nothing more to do — Domain DNS covers it:

Type: NS
Name: _acme-challenge
Value: beta-dns.freestyle.sh

That gives Freestyle authority over _acme-challenge.example.com alone. The rest of your zone stays entirely under your control, and the delegation stays in place between renewals so certificates keep reissuing without you doing anything.

Delegate at the level you are certifying. A wildcard for *.example.com is proven at _acme-challenge.example.com; one for *.app.example.com is proven at _acme-challenge.app.example.com.

Request The Certificate

The domain you pass is the parent. Requesting a wildcard for example.com produces a certificate for *.example.com:

import { Freestyle } from "freestyle";

const freestyle = new Freestyle();

await freestyle.domains.certificates.createWildcard("example.com");

The delegation is checked immediately — if the NS record is missing or points elsewhere, the call fails and names the nameserver it expected. Issuance itself happens in the background once the check passes, so poll for it:

const certificates = await freestyle.domains.certificates.list();
const wildcard = certificates.find((c) => c.domain === "example.com" && c.wildcard);

console.log(wildcard?.active, wildcard?.notAfter);

Or from the CLI:

freestyle domain cert wildcard example.com
freestyle domain cert list

A wildcard covers exactly one label. *.example.com matches app.example.com, but not example.com itself and not a.b.example.com — nested subdomains need their own wildcard on the parent, *.b.example.com.

Point The Wildcard At Freestyle

Type: CNAME
Name: *
Value: beta-web.freestyle.sh

See Domain DNS for apex domains and for checking that the record resolves.

Route The Subdomains

Ownership carries down: verifying example.com lets you route any subdomain of it without verifying each one. When every subdomain lands in the same place, one rule covers them all — a *.example.com domain on a TLS ingress rule matches any single-label subdomain, and creating it requests the wildcard certificate for you:

await freestyle.tls.rules.create({
  action: "allow",
  domain: "*.example.com",
  source: { public: true },
  destination: { vmId, port: 3000 },
});

When different subdomains land on different VMs, create a rule per hostname as each one appears:

await freestyle.tls.rules.create({
  action: "allow",
  domain: `${tenant}.example.com`,
  source: { public: true },
  destination: { vmId: tenantVmId, port: 3000 },
});

An exact rule beats the wildcard, so the two compose: a catch-all *.example.com rule can back per-tenant exact rules, and a hostname no rule serves is answered with a 404 by the edge — over a valid certificate, when a wildcard certificate is live. That 404 is a placeholder page that reloads itself until a route appears, and it can carry your branding.

A wildcard covers exactly one label here too: a *.example.com rule serves app.example.com, not example.com itself and not a.b.example.com.

Brand The Placeholder Page

A hostname under one of your verified domains that no rule serves gets the edge’s placeholder page: a 404 that names the hostname and reloads itself every 30 seconds, so a visitor who arrived a moment before the route did lands on the app without doing anything. By default it carries Freestyle’s wording. Set your own logo and name once for the account and every placeholder page under every domain you have verified wears them — including domains you verify later, so a customer of yours who brings their own domain is covered without any per-domain setup:

await freestyle.domains.placeholder.set({
  logoUrl: "https://cdn.example.com/logo.svg",
  productName: "Example Cloud",
});

The logo URL must be https; the visitor’s browser loads it directly. productName is optional — omit it for a logo alone. The page’s behaviour does not change: it is still a 404, it still reloads itself, and programs asking with anything other than Accept: text/html still receive the JSON error. freestyle.domains.placeholder.delete() returns the pages to the default.

Whose branding a page wears is decided by domain ownership, nearest parent first: tenant.example.com wears the branding of whoever verified example.com (or tenant.example.com itself). Where two accounts both hold a verification for the same nearest domain, the page stays unbranded rather than guess.

When To Use One

Reach for a wildcard when subdomains are created programmatically and you cannot enumerate them ahead of time — per-tenant hostnames, per-branch preview environments, per-user sandboxes. The certificate is what makes per-hostname rules instant: routing a new subdomain normally triggers its own issuance, an ACME round trip before the hostname serves traffic, while under a live wildcard the rule is the only step.

For a handful of fixed subdomains, skip it and let each hostname’s rule get its own certificate. That costs nothing extra to set up — the _acme-challenge delegation is required either way — it just means a new hostname waits on its own issuance before it serves.

esc