Kiln docs
v1.1.0
Get help
● Ship · Real messages

Troubleshooting
real error messages and what to do.

Start with the log and the console's status screen: most problems show up in one of them.

Logs/healthzAI · Stripe · CloudflareForms

01Reading the log

How Kiln runsCommand
npm start in a terminalThe terminal itself
systemdjournalctl -u kiln -f
PM2pm2 logs kiln
Dockerdocker logs -f kiln

Kiln's lines start with [kiln], the console's product pages with [console], the Admin Kit with [admin]. When the builder shows "Something went wrong on the server. The log has the details.", the full error is in the log. A quick health test: open <your address>/healthz; it answers {"ok":true,"version":"…"}.

Windows

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.

02The server does not start

  • Wrong Node version. Run node -v. Kiln needs 22.12 or newer.
  • Port in use (EADDRINUSE in the log). Another program uses port 8787, often an older Kiln still running. Stop it (sudo lsof -i :8787 shows which process) or set another PORT in .env.
  • better-sqlite3 errors such as "was compiled against a different Node.js version". This happens after upgrading Node. Run npm rebuild better-sqlite3, or npm ci again.
  • Cannot open the database. The user running Kiln must be able to write to the data/ folder (sudo chown -R kiln:kiln /opt/kiln/data for the systemd example).
  • Cannot find module …/dist/src/server/index.js. The server is not built: run npm run build.

03"The builder app is not built yet. Run: npm run build"

/app shows this when web/dist/ is missing. Run npm run build in the project folder and restart.

04The console owner cannot sign in (lost password or authenticator)

There is no reset by e-mail for the console owner, on purpose. On the server, in the Kiln folder:

node dist/src/server/recover-owner.js                          # lists the console members
node dist/src/server/recover-owner.js owner@your-agency.example    # prints a new one-time password
node dist/src/server/recover-owner.js owner@your-agency.example --reset-2fa   # also removes two-factor

Sign in at /admin with the printed password and change it. Every session of that account is signed out.

05The console setup code is lost

The code is printed only in the log, only while no console owner exists. Restart Kiln: each start prints a new code, and every printed code works until the owner is created. Once the owner exists, no code is printed.

06A saved key "cannot be decrypted"

The log shows [admin] secret "…" cannot be decrypted: was the secretKey changed?. ADMIN_SECRET_KEY is not the one the keys were saved with. Put the original key back in .env, or enter all keys again under Integrations (see Installation).

07Signing in to the builder

  • "Wrong e-mail or password." Check the address. Ask an owner of the console for a Reset link (Users page), or use Forgot password (needs e-mail to be set up; the link is valid 2 hours).
  • "Too many attempts. Wait 15 minutes and try again." More than 10 attempts in 15 minutes from the same address or for the same e-mail. Wait 15 minutes. If it happens to many people at once, see Behind a proxy below.
  • Sign-in seems to work but you land on the sign-in page again. The cookie is marked Secure (because the public URL starts with https://, or COOKIE_SECURE=1) but you opened Kiln over plain http://, for example http://<server ip>:8787. Browsers drop Secure cookies on http. Open the https address instead.
  • "This link is invalid, used or expired. Ask for a new one." Invite links last 7 days and work once.
  • "Sign-up is by invitation." Access → Anyone can sign up is off. Invite the person instead.

08Behind a proxy: everyone shares one address

If Kiln runs behind Caddy or nginx with TRUST_PROXY_HOPS=0, it sees every visitor as the proxy's address. Then sign-in, sign-up and form limits are shared by everybody, and "Too many attempts" or "Too many messages" appear quickly. Set TRUST_PROXY_HOPS=1 (one proxy) in .env and restart.

09AI errors

These messages come from Kiln's provider layer and appear in the builder and in the console's AI usage page. Credits for a failed call are always refunded.

MessageWhat to do
No AI key is configured. The operator adds one in the console (Integrations).Add the key for the selected provider.
Anthropic rejected the API key. / OpenAI rejected the API key. / OpenRouter rejected the API key.The key is wrong, revoked or from another account. Create a new one and save it again.
The Anthropic key has no access to this model.Your Anthropic account or key cannot use this model. Choose another model or check the account.
Anthropic does not know this model id. Check AI → Model in the console.Typo in AI → Model id. Use e.g. claude-opus-5-5.
Set the OpenAI model id under AI → Model in the console. (or OpenRouter)These providers have no default model. Enter one.
OpenAI error 404: … / OpenRouter error …: …Usually an unknown model id, or a model without JSON-schema output.
Anthropic rate limit reached. Wait a minute and retry.Your provider account's rate limit. Wait, or raise the limit with the provider.
The answer was cut off (max tokens). Try fewer pages or sections at once.The page was too long for one answer. Remove sections from that page in the plan, or split the page.
The model declined this request. Rephrase the brief and try again.Change the brief's wording.
The model returned text that is not valid JSON. Retry.Retry. If it repeats with OpenAI or OpenRouter, pick a model with structured output.
Could not reach Anthropic. Check the server's internet connection. / Could not reach OpenAI.The server cannot reach the provider: DNS, firewall or an outage.
Not enough credits: this needs N, the workspace has M.Add credits on the Workspaces page, or let the client buy a pack.
Too many AI requests in a minute. Wait a moment and try again.One person started more than 30 AI actions in a minute (each translated page counts). Wait a minute.
Set the endpoint URL under AI in the console.The provider is Any OpenAI-compatible endpoint but AI → Endpoint URL is empty.
Could not reach The AI endpoint. / The AI endpoint error 404: …Wrong endpoint URL (it must end before /chat/completions), the server cannot reach it, or the model id is unknown there. For Ollama on another machine, use that machine's address, not localhost.

10Rebuild from URL errors

MessageWhat to do
Enter a web address like https://example.com.The address could not be read. Paste the full address.
The domain … does not resolve.Typo, or the domain has no DNS record. Open it in a browser first.
That address is not on the public internet.The address points to a private, local or reserved network. Only public sites can be read.
Only http and https addresses can be imported. / Only the standard web ports can be imported. / Addresses with a user name or password cannot be imported.Use a plain https:// address without a port (80, 443 and 8080 are allowed) or credentials.
Could not reach ….The site did not answer within 10 seconds, refused the connection, or one of its addresses is private.
… answered 403. (or another number)The site refused the request, often a firewall or bot protection. Copy the text into the brief by hand.
That address is not a web page.The address returns a file or data, not HTML. Paste the address of a page.
Too many redirects.More than three redirects. Paste the final address from the browser's address bar.
No readable text on that page. Sites built only with JavaScript cannot be read.The page builds its text in the browser. Copy the text into the brief by hand.
Too many imports in a minute. Wait a moment.More than 10 imports by one person in a minute.

11Cloudflare errors

Cloudflare's own messages are shown after Cloudflare: together with Cloudflare's error code.

  • "Cloudflare is not set up…" The account id (Hosting) or the API token (Integrations) is missing, or the account id is not 32 hex characters.
  • No workers.dev subdomain. A new Cloudflare account has none. The token test and every deploy fail. Open Workers & Pages in Cloudflare and set up the subdomain (step 1).
  • Authentication or permission errors. The token lacks a permission, or the account is not in its Account Resources. Recreate it with the permissions listed in Publishing to Cloudflare.
  • "No zone for … in this Cloudflare account. Add the domain to Cloudflare first." The custom domain must be a zone in the same Cloudflare account, and the token's Zone Resources must include it.
  • "Publish the site to Cloudflare first." Deploy once before attaching a Cloudflare domain.
  • "The … plan does not include Cloudflare publishing." Turn on Cloudflare publishing for that plan.

12Stripe

The webhook's answer is visible in Stripe → Developers → Webhooks → your endpoint → the event.

Webhook answerCause
503 "Webhook secret not set"Add the signing secret under Integrations.
400 "Missing Stripe-Signature header"Something other than Stripe called the URL, or a proxy strips the header.
400 "Stripe signature does not match"The signing secret belongs to another endpoint, or to test mode while the event is live (or the reverse). Copy the secret of this endpoint again. A proxy that changes the request body also causes it.
400 "Stripe event timestamp outside the tolerance"The server's clock is more than 5 minutes off. Turn on time sync (timedatectl set-ntp true).

In the builder: "Online payments are switched off." (turn on Sell plans and credit packs), "Stripe is not set up." (no secret key), "The … plan has no Stripe price id yet." (add it on the Plans page), "No Stripe customer for this workspace yet." (the portal works after the first payment), "Stripe: …" (Stripe's own message, for example an invalid price id, or the customer portal not activated in Stripe).

13Form messages do not arrive

  1. Public URL. Forms post to <public URL>/f/<site id>. If the public URL is empty or wrong, the form posts to the wrong place. Set it. Sites on the built-in hosting pick it up at once; Cloudflare deployments and exported ZIPs contain the address from the time they were made, so deploy or export them again.
  2. Reachable over https. Open <public URL>/healthz from outside your network.
  3. Cross-site request. Sites on other domains (Cloudflare, exports, custom domains) first send an OPTIONS request. Your proxy or firewall must pass OPTIONS and POST to /f/ unchanged.
  4. Rate limit. At most 5 messages per 10 minutes per sender and site; then "Too many messages. Try again in a few minutes." See Behind a proxy above.
  5. Rejected input. The form needs a valid e-mail ("Enter a valid e-mail address.") and a message ("Write a message."), and at most 20,000 characters.
  6. Honeypot. The form has a hidden field that only bots fill in. Such messages are answered with success and dropped. A browser extension that fills every field can trigger this.
  7. E-mail notices. Messages are always stored in the Forms tab. The e-mail to workspace owners needs the Resend key, the sender (E-mail section) and E-mail workspace owners about new form messages.

14The form webhook does not fire

The webhook is called after a message is stored; failures are not shown or logged, and not retried. Check:

  1. The message arrived in the Forms tab. If not, see Form messages do not arrive above: the webhook only runs for stored messages (not for honeypot or rejected ones).
  2. The address starts with https://. Any other address is removed when the site is saved; reload the editor and look at the field.
  3. The site was published after the webhook was set. A published site uses the webhook of its published version.
  4. No redirect. Kiln does not follow redirects. Use the exact address your service gives you.
  5. Public address, standard port. Addresses that resolve to a private network, or use a port other than 443 or 8080, are refused.
  6. Answer within 5 seconds. A slower endpoint is cut off.
  7. Test the endpoint from the server: curl -X POST -H 'content-type: application/json' -d '{"fields":{}}' https://your-webhook-address.

15Analytics do not load

  • The consent banner is on. Google Analytics, Tag Manager and the Meta Pixel load only after a visitor clicks Accept. A visitor who clicked Decline is not asked again in that browser. To test, open the site in a private window and accept, or clear the site's data in the browser. Plausible does not wait for consent.
  • The id was dropped. An id in the wrong format is removed when the site is saved: GA4 G-…, Tag Manager GTM-…, Meta Pixel digits only, Plausible the bare domain. Reload the editor and look at the field.
  • Not published yet. Analytics run on the published site only, never in the editor canvas. Publish after you change them.
  • Astro export. The Astro project does not include analytics; add them in the project yourself.
  • Ad blockers in the visitor's browser block these scripts. Test in a browser without one.

16E-mails are not sent

Check the E-mail can be sent status check. The Resend Test may say "Resend rejected the key (a sending-only key cannot list domains; it may still send)": a sending-only key can still work. The sender's domain must be verified in Resend.

17A custom domain stays "Waiting for DNS"

  • "No TXT record _kiln-verify.<domain> with the expected value yet. DNS changes can take up to an hour." The TXT record is missing, has a typo, or has not spread yet. The record's name is _kiln-verify.<domain> (some DNS panels add the domain themselves: then enter only _kiln-verify.www), and its value is exactly kiln-verify=<token> from the instruction. Check it with dig TXT _kiln-verify.www.bakery.example, wait, and press Verify again.
  • "Set the public URL of this server in the console first." Built-in hosting domains need Hosting → Public URL.
  • Until it is verified, the domain is not served: visitors on it do not see the site.

18A published site address shows the builder or "Not found"

  • The sites domain in Hosting must be exactly the part after the site address, e.g. sites.your-agency.example.
  • The site must be published. An unknown site address under /s/ answers "Not found".
  • Your proxy must pass the original Host header (Caddy does by default; in nginx use proxy_set_header Host $host;).

19Uploads fail

"Only PNG, JPEG, WebP and GIF images." (SVG and other files are refused), "Images up to 8 MB.". If your proxy has a smaller body-size limit (nginx's default is 1 MB), raise it, e.g. client_max_body_size 10m;.

20A client's changes are "Not saved"

A member with the client role sees "Your role can change texts and images. Ask the agency to change sections, layout or design." when a save contains anything besides text and image changes, for example a page address, a site setting in the Page tab, or a link address. Undo that change (⌘Z) and the next save goes through, or ask an editor or owner to make it.

21"Missing request header"

The builder sends an x-kiln header with every change. A proxy or firewall that strips unknown headers causes this

  1. Let the header through.