Skip to content

FAQ and Troubleshooting

Theodor Dimitriou edited this page Sep 24, 2026 · 2 revisions

Short answers to common questions about what AstroBaaS is and is not, followed by fixes for the problems operators hit most often. It is for anyone evaluating, installing or running an install.

For anything not covered here, GET /api/health/deep is the first place to look. It names most misconfigurations (mail, CORS, site address, backups, payment return URLs) as warnings. It is open to a signed-in admin, or to a caller holding HEALTH_TOKEN.

Frequently asked questions

Is AstroBaaS a WordPress replacement?

For a small site or shop, it can be. It is not a WordPress replacement at scale. It has posts, pages, media, custom content types, revisions, autosave, custom taxonomies, moderated comments, themes and a built-in shop, plus importers for WordPress (WXR) and WooCommerce. Editorial depth is thinner than WordPress, and there is no plugin marketplace. It is pre-alpha. See Admin-Tour and Importing.

Is it multi-tenant?

No. One install serves one site. The storage layer does not know who is asking, and there is no database row-level security. If you host several unrelated businesses, run a separate instance for each. See Security.

Which database does it use?

It depends on what you set:

You set You get
Nothing lowdb: one db.json file. Good for local development.
DATABASE_URL=file:./data/astrobaas.db libSQL doc-blob on a local SQLite file
DATABASE_URL=libsql://… + DATABASE_AUTH_TOKEN libSQL doc-blob on Turso
Either URL + DATABASE_DRIVER=relational Relational libSQL. Recommended for production.

There is no Postgres or MySQL driver yet. A Postgres adapter is listed as a good first contribution. See Backups-and-Storage.

How do I run it headless?

Run the CMS as usual, then:

  1. Mint an API key in Admin → API Keys (or POST /api/keys) and give it scopes.
  2. Add your frontend's origin to CORS_ORIGINS.
  3. Call the REST API with Authorization: Bearer <key>, or use the typed client from astrobaas/client.
  4. For a shop, set Site URL to the storefront's address, so payment providers send buyers back there.

The contract is published at /openapi.json and /llms.txt, and there is an MCP server (npx -y astrobaas-mcp) for AI agents. See Headless-and-REST-API and SDK-CLI-and-MCP.

Can I install it with npm install astrobaas?

Not to run a site. Cloning the repository is the supported install. The npm package (astrobaas@alpha) provides astrobaas/client, astrobaas/core, astrobaas/plugins and the CLI for use in your own projects. See Installation.

Why can't I install plugins from the dashboard?

It is a deliberate design choice. Extensions come in two tiers:

  • Declarative manifests (JSON) can be installed at runtime from the admin or the install endpoint. They can register content types, head tags, webhooks, design tokens, stylesheets and sections. They never run code.
  • Code plugins and themes are TypeScript modules compiled in at build time. Add a folder and one import, or name an external package in ASTROBAAS_PLUGINS, and redeploy.

Astro compiles the server at build time, so code that runs on the server has to be there at build time. The trade gives you type safety, a strict hash-based CSP, and no code-upload attack surface; you give up click-to-install. See Plugins and Themes.

Is commerce a paid add-on?

No. The shop is part of the GPL core. It is off by default; switch it on at Admin → Settings → Selling. Paid modules are trade-specific extras that are not in the repository. See Commerce and Contributing-and-Licensing.

Does it check a licence or phone home?

No. There is no licence check anywhere in the code, and nothing reports home.

Which payment providers are supported?

Stripe, PayPal and Klarna are built in, plus bank transfer and cash on delivery as manual methods. Others can be added from a plugin. None has been round-tripped against the providers' live sandboxes, so test yours there first. See Payments.

Troubleshooting

Login is refused with the default admin in production

Symptom: signing in as admin@local / admin returns 403 SEED_PASSWORD_REFUSED, or you never knew the admin password.

Cause: on a production build (what npm run build produces, whether or not NODE_ENV is set), the well-known seed password is refused, because in a public repository a default credential is a published one. A fresh production install also generates a random admin password and prints it once to stdout, unless ADMIN_PASSWORD was set before first boot.

Fix:

npm run reset-password

Alternatively, look for the generated password in the first boot's log, or set ADMIN_PASSWORD before the first start. ALLOW_SEED_PASSWORD=1 turns the refusal off, but only use that on a trusted private deployment. npm run reset-password edits db.json only; with a DATABASE_URL set it refuses to run.

Also check AUTH_SECRET. A production build refuses a missing, short or placeholder secret, and nobody can sign in until it is replaced (openssl rand -hex 32).

Sign-in does nothing over plain http

Symptom: a production build served over http:// accepts the password but you are never signed in, or writes fail the CSRF check.

Cause: in production, session and CSRF cookies carry the Secure flag, and browsers do not store Secure cookies from an http:// page.

Fix: serve over HTTPS behind a reverse proxy. To test a production build on plain localhost only, set COOKIE_SECURE=0. Behind a proxy, also set TRUST_PROXY=1 so the app sees X-Forwarded-Proto.

The browser says "blocked by CORS policy"

  1. Check CORS_ORIGINS. The exact origin (scheme, host and port) must be on the list: https://shop.example.com is not https://www.shop.example.com or http://localhost:3001. Origins are separated by spaces or commas. Restart after changing it.
  2. Check custom headers. Only Content-Type, Authorization, X-CSRF-Token and Idempotency-Key are allowed on a cross-origin request. Any other header fails the preflight.
  3. Read the real status. Since 0.1.3, refusals (401, 403 CSRF_FAILED, 403 INSUFFICIENT_SCOPE, 411, 413, 429, maintenance 503) carry CORS headers, so your code sees the status and error.code. Look at the network tab rather than the console message. Only an unexpected 500 can still arrive without CORS headers; check the server log for it.

Do not "fix" it with CORS_ORIGINS=*. That lets any website place orders and post forms from a visitor's browser, and the health check warns about it. See Security.

Media URLs point at the wrong host (or use http)

Symptom: image URLs in API responses point at an internal host or port, at the CMS rather than a public address, or use http:// on an HTTPS site, so images break on the storefront.

Cause: with no configured address, media URLs are worked out from the host each request arrives on. That is right for the admin and wrong for a storefront on another domain or a server-to-server call.

Fix: set Admin → Settings → Address of this CMS (public_site_url), for example https://cms.example.com. A declared value always wins. If URLs come out as http://, the app is not seeing X-Forwarded-Proto: set TRUST_PROXY=1 and make sure the proxy sends the header. The deep health check warns while the address is empty. There is no MEDIA_BASE environment variable.

With nginx, point /uploads/ at the shared uploads directory, not at a release's dist/client/uploads. Otherwise every file uploaded since the build returns 404.

systemd fails with 200/CHDIR after a release

Symptom: after a deploy the service will not start, and journalctl shows status=200/CHDIR until systemd gives up.

Cause: the release directory is not enterable by the app user. An earlier deploy.sh staged releases in a 0700 temporary directory and rsync -a copied that mode onto the release.

Fix: open the release root to the app user, then restart:

sudo chmod 0755 /var/www/<site>/releases/<timestamp>
sudo chmod -R g+rX,g-w /var/www/<site>/releases/<timestamp>
sudo systemctl restart <service>

Current deploy.sh and remote-activate.sh set these modes before switching current. If you deploy another way, do the same. See Deployment.

Emails are not sent

Symptom: password resets, form notifications or order confirmations never arrive.

  1. Check the health check. /api/health/deep → email_channel. A warn saying mail "is not being sent" means the transport is console (the default), or the configuration was rejected and fell back to console. The message names the variable.

  2. Run the mail test on the server:

    node dist/mail-test.mjs you@example.com

    It prints the SMTP conversation and the server's reply, and exits non-zero on failure.

  3. Common causes: SMTP_PASS not single-quoted in the env file (gives a 535); EMAIL_FROM not an address the account may send as (553/550); no STARTTLS on the port (use 465 with SMTP_SECURE=1).

  4. No recipient for form notifications. Set Settings → "Send form submissions to" (contact_notify_email), or at least admin_email.

  5. A 250 is not delivery. Check the spam folder, and the email log under Admin → Background jobs.

See Email.

"Too many sign-in attempts"

Symptom: sign-in answers 429, or 403 POW_REQUIRED.

Throttle (15-minute window) Effect
10 attempts for one address + email 429; wait for the window to pass
30 failed sign-ins from one address, any email 429 for that address (LOGIN_IP_FAILURE_LIMIT)
5 failed sign-ins for one account, from anywhere Not a lockout: a proof-of-work is required, which the admin sign-in page solves by itself
10 two-factor code attempts per account Refused until the window passes (TWOFA_ATTEMPT_LIMIT)

If everyone is throttled at once behind a reverse proxy, the app is probably seeing the proxy's address for every user. Set TRUST_PROXY=1 (only behind a proxy you control). A script calling the API should get 403 POW_REQUIRED only after repeated failures; fix the credentials rather than retrying. With several replicas, set RATE_LIMIT_STORE=libsql so counters are shared.

Public forms answer 403 from another site

Symptom: a content type with writable: 'public' (an enquiry, a booking request, an RSVP), or its file upload, answers 403 CSRF_FAILED when posted from a storefront on another origin.

Cause: before 0.1.3, the cookie-less cross-origin exemption covered checkout, contact and newsletter, but not the form builder's own forms.

Fix: upgrade to 0.1.3 or later, and make sure the storefront's origin is in CORS_ORIGINS. After that:

  • a type that is not public still answers 404;
  • a request that carries a session cookie still needs the CSRF token;
  • an origin not on the list is still refused.

See Upgrading.

/api/products answers 404

The shop is switched off, which is the default. Switch it on at Admin → Settings → Selling, or import a catalogue. See Commerce.

A payment provider does not appear at checkout

It must be listed in PAYMENTS_ENABLED and have every required variable set. Admin → Settings → Payment providers lists the missing or faulty variables by name. Restart after editing the env file. See Payments.

Buyers land on the CMS after paying

Set Site URL to the storefront's address. The CMS does not serve /checkout/success. The deep health check reports this as payment_return_urls.

Changes to the CSP have no effect

CSP allow-lists (CSP_*) are read at build time. Rebuild after changing them. astro dev sends no CSP at all, so test against the built server.

A restore says "400"

Restore only works onto the same kind of driver. A JSON (lowdb) archive cannot be restored onto a DATABASE_URL install, and a SQLite snapshot cannot be restored onto lowdb or Turso. After a SQLite restore, restart the site. See Backups-and-Storage.

See also

Clone this wiki locally