Hosting published sites
addresses, DNS and HTTPS.
Kiln can serve the sites it builds from the same server it runs on. This page sets up the addresses and HTTPS. If you would rather host sites on Cloudflare, see Publishing to Cloudflare. You can use both.
01How addresses work
Kiln looks at the host name of every request:
- The host of the Public URL (for example
build.your-agency.example) is the builder and the console. - A host under the sites domain (for example
bakery.sites.your-agency.example) shows the published site whose address isbakery. - A custom domain added to a site (for example
www.bakery.example) shows that site.
Published sites are a frozen copy. Later edits stay drafts until someone clicks Publish changes.
Without a sites domain
If Hosting → Domain for published sites is empty, every published site is still reachable at <public URL>/s/<site>/, for example https://build.your-agency.example/s/bakery/. This needs no extra DNS or certificates and is fine for testing and for client previews. Links inside the site are rewritten to work under that path.
02What you need
- A domain for the builder, e.g.
build.your-agency.example. - A domain for sites, e.g.
sites.your-agency.example. Do not put the builder's own domain under it. - A reverse proxy with HTTPS in front of Kiln. The examples use Caddy.
The server, the domains and the DNS provider are your own accounts. Sites on the built-in hosting are served by your server, so their traffic uses your server's bandwidth. Caddy and Let's Encrypt are free. See Costs.
03Step 1: DNS records
At your DNS provider, create these records, all pointing to your server's IP address:
| Type | Name | Value |
|---|---|---|
| A | build.your-agency.example | your server IP |
| A | sites.your-agency.example | your server IP |
| A | *.sites.your-agency.example | your server IP |
The wildcard record (*) makes every <site>.sites.your-agency.example reach your server without a new record per site. The plain sites.your-agency.example record is for clients who point a custom domain to it with a CNAME. Add AAAA records too if your server has IPv6.
The commands on this page are for a Linux or macOS terminal. On Windows, use WSL (Windows Subsystem for Linux) with Ubuntu, install Node.js inside it and type the commands there. Native Windows (without WSL) is not supported.
04Step 2: Kiln settings
In .env:
HOST=127.0.0.1
TRUST_PROXY_HOPS=1
KILN_PUBLIC_URL=https://build.your-agency.example
HOST=127.0.0.1makes Kiln reachable only through the proxy.TRUST_PROXY_HOPS=1tells Kiln there is one proxy in front of it. Kiln then takes the visitor's address from the proxy'sX-Forwarded-Forheader. Rate limits and sign-in protection need the real address. Leave it at0when nothing sits in front of Kiln: otherwise anyone could fake their address.- A public URL that starts with
https://(in.envasKILN_PUBLIC_URLor in the console) gives builder sign-in cookies the Secure flag.COOKIE_SECURE=1in.envforces it in any case.
In the console, under Hosting:
- Public URL of this server:
https://build.your-agency.example(locked if set in.env). - Domain for published sites:
sites.your-agency.example(no*., nohttps://).
Restart Kiln after changing .env.
05Step 3: HTTPS with Caddy
A wildcard certificate (*.sites.your-agency.example) can only be issued with a DNS challenge: the proxy proves it owns the domain by creating a DNS record. Caddy does this through a DNS-provider module. The example below uses the Cloudflare DNS module, so the DNS of your-agency.example must be managed on Cloudflare. Modules exist for many other DNS providers.
- Get a Caddy build that includes the module
github.com/caddy-dns/cloudflare. Either use the download page on caddyserver.com (choose the module before downloading) or build it withxcaddy build --with github.com/caddy-dns/cloudflare. - In Cloudflare, create an API token with Zone · DNS · Edit (and Zone · Zone · Read) for
your-agency.example. This token is for Caddy only; it is not the token Kiln uses for publishing. - Give the token to Caddy as the environment variable
CF_API_TOKEN(for example in the Caddy systemd unit:Environment=CF_API_TOKEN=...). - Save this as
/etc/caddy/Caddyfile:
{
email you@your-agency.example
}
# The builder and the operator console
build.your-agency.example {
reverse_proxy 127.0.0.1:8787
}
# Every published site on the sites domain
sites.your-agency.example, *.sites.your-agency.example {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy 127.0.0.1:8787
}
- Reload Caddy:
sudo systemctl reload caddy.
Caddy passes the original host name to Kiln and adds X-Forwarded-For and X-Forwarded-Proto, which is all Kiln needs. Kiln trusts these headers only when TRUST_PROXY_HOPS is above 0; with 0 it takes the scheme (http or https) of published sites from the public URL. The builder domain gets an ordinary certificate; only the sites block uses the DNS challenge.
Check it: open https://build.your-agency.example/healthz. It should answer {"ok":true,...}. Then publish a site and open https://<site>.sites.your-agency.example/.
Automatic certificates per site are not used
Caddy's "on-demand TLS" (a certificate for any host name on first visit) needs an ask endpoint that tells Caddy which names are allowed. Kiln has no such endpoint, so do not use on-demand TLS: anybody could make your server request certificates for arbitrary names. Use the wildcard certificate above, and one site block per custom domain.
nginx instead of Caddy
Any proxy works if it forwards the Host header, sets X-Forwarded-For and X-Forwarded-Proto, and has a certificate for the names it serves. For the wildcard, use certbot with a DNS plugin for your DNS provider.
06Custom domains for clients
A client's own domain (e.g. www.bakery.example) can point at the built-in hosting. The site's plan must allow custom domains, only a workspace owner (or staff) can add one, and the public URL must be set in the console ("Set the public URL of this server in the console first." otherwise).
Kiln serves a custom domain only after the domain's owner has proved control of it with a DNS record, so nobody can attach a domain they do not own.
- The builder: open the site → Publish → Custom domains → enter
www.bakery.example, choose Built-in hosting, Add domain. The domain appears with Waiting for DNS and an instruction like: "Add a TXT record_kiln-verify.www.bakery.examplewith the valuekiln-verify=<token>, and a CNAME record forwww.bakery.examplepointing tosites.your-agency.example. Then press Verify." (Without a sites domain, the CNAME points to the builder's host name.) - The client's DNS: add both records exactly as shown. A bare domain (
bakery.example) cannot have a CNAME at most providers: use anArecord to your server IP instead (the TXT record is still needed). - Verify: back in the Publish dialog, press Verify next to the domain. If the TXT record is not visible yet, you get "No TXT record _kiln-verify.… with the expected value yet. DNS changes can take up to an hour." Wait and press Verify again. Once verified, the domain shows Active and Kiln serves the site on it.
- Click Publish changes so the site's links and canonical tags use the new address.
- Caddy: each custom domain needs its own block, because the wildcard certificate does not cover it:
www.bakery.example {
reverse_proxy 127.0.0.1:8787
}
Reload Caddy after adding it. Caddy issues the certificate on the first request, once the client's DNS points to your server.
Verification checks only the TXT record. If a verified domain does not load, check the client's CNAME (or A) record and the Caddy block. Domains published to Cloudflare are not verified this way: Cloudflare attaches them from a zone in your own Cloudflare account.
The easier way for custom domains: Cloudflare
If you have many client domains, editing the Caddyfile for each one gets tedious. The recommended alternative: add the client's domain to your Cloudflare account, publish the site to Cloudflare, and attach the domain there. Cloudflare then issues and renews the certificate, and your server needs no change. See Publishing to Cloudflare.
07Forms on hosted sites
The contact form on every published site posts to <public URL>/f/<site id>. That is why the public URL must be set and reachable over https. Messages appear in the site's Forms tab in the editor.
08Badge
Hosting → Badge on sites of plans that keep it puts a small link (for example "Made with YourBrand") in the footer of published sites. Plans with No badge on sites turned on do not show it. Empty text: no badge anywhere.