Repository navigation
Configuration
A reference for every environment variable AstroBaaS reads, grouped by area, plus the few admin settings that change how the install behaves. It is for operators setting up or tuning an install; if you are installing for the first time, start with Installation.
The annotated source of truth is .env.example. This page also lists a number of variables the code reads that .env.example does not mention.
-
Most variables are read at runtime from
process.env. Changing them needs a restart, not a rebuild. -
Some are read at build time by
astro.config.tsand baked into the bundle. See Build-time variables. -
Nothing reads
.envautomatically in production.npm startdoes not load it. Usenode --env-file=.env ./dist/server/entry.mjs, systemd'sEnvironmentFile=, or your container platform's environment. In development,npm run devdoes not put.envintoprocess.enveither; usenode scripts/dev.mjs, which loads.env.localthen.envand runsastro dev. - Secrets belong in the environment, not in admin settings. Payment keys, SMTP passwords and backup credentials are deliberately environment-only, because part of the settings table has a public read path.
-
Boolean flags. Most accept
1/true; several (maintenance, staging) also accepton/yes. Where a flag is on by default,0,false,nooroffturns it off. When in doubt, use1and0.
These are read when npm run build runs. Setting them only in the runtime environment (a systemd unit, shared/.env) has no effect.
| Variable | Default | What it does |
|---|---|---|
SITE_URL |
http://localhost:4321 |
Astro's site: the fallback origin for absolute URLs in sitemap.xml, rss.xml, canonical and Open Graph tags. The runtime Site URL setting takes precedence (see Runtime settings). Some server code also reads it at runtime as a fallback. |
CSP_IMG_SRC, CSP_FONT_SRC, CSP_CONNECT_SRC, CSP_WORKER_SRC, CSP_FRAME_SRC
|
unset | Extra origins appended to that CSP directive. Space- or comma-separated. |
CSP_SCRIPT_SRC, CSP_STYLE_SRC
|
unset | Extra origins for script-src / style-src. Astro adds the hashes of its own scripts and styles; there is no 'unsafe-inline' for scripts. |
CSP_CHILD_SRC |
unset | When set, adds a child-src 'self' <origins> directive. |
CSP_ALLOW_WASM |
off |
1 adds 'wasm-unsafe-eval' to script-src, for WebAssembly renderers. |
ANALYTICS_PROVIDERS |
unset | Comma-separated analytics connector ids (for example ga4,meta-pixel). Adds exactly the origins those connectors need to the CSP. The tracking IDs themselves are runtime settings; the admin reports a mismatch between the two. |
The default policy is 'self' for most directives, with https: and data: allowed for images, https: for media, Google Fonts for fonts and styles, frame-ancestors 'none', object-src 'none' and base-uri 'self'. The logic is in src/lib/csp-config.ts. For WebGL and animation libraries loaded from a CDN, see FRONTEND_EFFECTS.md.
| Variable | Default | What it does |
|---|---|---|
AUTH_SECRET |
dev-only fallback (development) | HMAC key for session and CSRF cookies. Required in production: a missing value, one shorter than 16 characters, or the .env.example placeholder is refused and nobody can sign in. Generate with openssl rand -hex 32 or npx astrobaas secret. |
NODE_ENV |
unset (npm start sets production) |
Production security rules are keyed on the build itself, so a built server is treated as production whether or not this is set. Setting it is still recommended. |
COOKIE_SECURE |
on in production, off in development |
1/0 forces the cookie Secure flag. Use 0 to test a production build over plain http://localhost. |
HOST, PORT
|
Node adapter defaults | Address and port of the built server (the @astrojs/node standalone server). The Docker image sets 0.0.0.0:4321; the reference systemd unit uses 127.0.0.1:3002. |
ALLOW_SEED_PASSWORD |
unset |
1 lets the seeded admin password sign in on a production build. Only for a trusted private deployment. |
Read once, on the first boot of an empty database. Changing them afterwards does nothing.
| Variable | Default | What it does |
|---|---|---|
ADMIN_EMAIL |
admin@local |
Address of the first admin account. Set a real address so password resets can reach you. |
ADMIN_NAME |
Admin |
Display name of that account. |
ADMIN_PASSWORD |
admin in development; random in production |
Its password. In production with none set, a random password is printed once to stdout. |
SEED_DEMO_CONTENT |
on |
0 skips the two demo blog posts on a fresh install. Removes nothing from an existing one. |
| Variable | Default | What it does |
|---|---|---|
SITE_URL |
see above | Build-time site origin. |
STAGING (alias ASTROBAAS_STAGING) |
off |
1 marks the install as a staging copy: noindex everywhere, Disallow: / in robots.txt, empty sitemap and feed, no outgoing webhooks, no analytics. One-way: nothing in the environment can force indexing on. See Staging and maintenance. |
LEGACY_RECOVERY |
on | Suggestions of likely matches on 404 pages (and at /api/recovery/match). off, false or 0 turns them off. |
| Variable | Default | What it does |
|---|---|---|
DATABASE_URL |
unset (lowdb JSON file) | Selects the storage engine. file:./path.db is a local SQLite file via libSQL; libsql://... is a remote libSQL/Turso database. |
DATABASE_AUTH_TOKEN |
unset | Auth token for a remote libsql:// database. |
DATABASE_DRIVER |
unset (doc-blob) | With a libSQL DATABASE_URL: unset stores the whole dataset in one row (last-write-wins); relational stores one row per entity (row-level concurrency, recommended for production). |
DB_PATH |
./db.json |
Path of the JSON database. Ignored when DATABASE_URL is set. The Docker image sets /app/data/db.json. |
MIGRATION_LOCK_WAIT_MS |
120000 |
How long a booting process waits for another process's schema migration before failing its init. The next request tries again. |
See Installation for choosing a driver, and STORAGE.md for the details.
| Variable | Default | What it does |
|---|---|---|
UPLOADS_DIR |
public/uploads |
Where public uploads are stored. Must be persistent. The Docker image sets /app/data/uploads. |
PRIVATE_UPLOADS_DIR |
private-uploads/ beside the database |
Files attached to public forms. Kept out of UPLOADS_DIR, which is public. Must be persistent and writable. An install with files in the old default (./private-uploads) keeps using it and logs how to move them. |
MEDIA_KEEP_ORIGINALS |
on |
0 keeps only the generated web derivatives and discards the uploaded original. |
MEDIA_ORIGINAL_EXIF |
strip |
By default the EXIF block (GPS, device serial) is removed losslessly from the kept original. keep retains the file byte for byte. |
MEDIA_MAX_VIDEO_MB |
100 |
Largest video upload accepted, in megabytes. Raise your reverse proxy's body limit to match (this value plus 10%) on /api/media/upload and /api/media/replace. |
MEDIA_SCAN |
off |
clamd (also clamav, 1, on) scans uploads with a ClamAV daemon. |
MEDIA_SCAN_HOST / MEDIA_SCAN_PORT
|
127.0.0.1 / 3310
|
clamd TCP address. |
MEDIA_SCAN_SOCKET |
unset | clamd UNIX socket path; takes precedence over host and port. |
MEDIA_SCAN_TIMEOUT_MS |
10000 |
Scanner timeout. |
MEDIA_SCAN_MAX_BYTES |
26214400 (25 MB) |
Upper bound of bytes streamed to the scanner. |
MEDIA_SCAN_FAIL |
closed | When scanning is on and the scanner cannot be reached, uploads are refused. Only the exact value open lets them through. |
MEDIA_PUBLIC_BASE |
unset | Read only by the admin's products screen, to resolve product image paths that are served by a separate storefront. |
Where uploaded files are served from is a runtime setting, not a variable: Address of this CMS. See below.
| Variable | Default | What it does |
|---|---|---|
TRUST_PROXY |
off |
1 honours X-Forwarded-For / X-Real-IP for the client IP (rate limits, login throttle) and X-Forwarded-Proto for absolute URLs. Set it only behind a reverse proxy you control, with the app port not reachable directly. |
CORS_ORIGINS |
unset | Origins allowed to call /api/* cross-origin, space- or comma-separated, or *. Credentials are never allowed; headless clients authenticate with a bearer API key. |
HEALTH_TOKEN |
unset | Lets a deploy script call GET /api/health/deep with Authorization: Bearer <token>. Use 32+ random characters. 16–31 works with a warning; under 16 it is ignored. Unset means admin session only (everyone else gets 404). |
SANITIZE_STRICT_CLASSES |
off |
1 makes stored CSS classes a closed set. Turning it on strips plugin editor sections out of content on save. |
CAPTCHA_BITS |
15 |
Difficulty of the built-in proof-of-work challenge, in leading zero bits. Clamped to 8–22. |
WEBHOOK_ALLOW_PRIVATE |
off |
1 allows webhook targets on private or internal addresses. For local development only. |
| Variable | Default | What it does |
|---|---|---|
RATE_LIMIT_PER_MIN |
60 |
Requests per minute per IP for anonymous /api/* traffic. |
STAFF_RATE_LIMIT_PER_MIN |
1800 |
Per-minute budget for a signed-in staff member, counted per user. |
RATE_LIMIT_API_KEY_PER_MIN |
6000 |
Per-minute budget for a request with an API key, counted per key. |
RATE_LIMIT_STORE |
in-process |
libsql (with a libSQL DATABASE_URL) shares counters across replicas. Without it each process counts separately. |
RATE_LIMIT_CHECKOUT_PER_MIN |
10 |
Order placement. |
RATE_LIMIT_QUOTE_PER_MIN |
30 |
Order quotes. |
RATE_LIMIT_PAYMENT_START_PER_MIN |
10 |
Starting a payment. |
RATE_LIMIT_SEARCH_PER_MIN |
30 |
Catalogue searches. |
RATE_LIMIT_WEBHOOK_PER_MIN |
600 |
Incoming payment-provider webhooks (a separate, generous bucket). |
LOGIN_IP_FAILURE_LIMIT |
30 |
Failed sign-ins allowed per IP before it is throttled. |
LOGIN_ACCOUNT_POW_AFTER |
5 |
Failed attempts on one account before a proof-of-work challenge is required. |
TWOFA_ATTEMPT_LIMIT |
10 |
Second-factor attempts allowed. |
PUBLIC_API_CACHE_SECONDS |
30 |
s-maxage for anonymous public API reads (maximum 86400). 0 turns shared caching off. |
PUBLIC_API_CACHE_SWR_SECONDS |
300 |
stale-while-revalidate for the same responses (maximum 604800). |
The per-IP-and-account login throttle (10 attempts per 15 minutes) is not configurable. Signed-in or keyed requests are always sent Cache-Control: private, no-store.
| Variable | Default | What it does |
|---|---|---|
EMAIL_TRANSPORT |
console |
console logs messages; smtp sends through your mail server; webhook POSTs each message as JSON to your endpoint. A misconfigured transport falls back to console with a warning. |
EMAIL_FROM |
unset | Sender. Required for smtp. A non-ASCII display name is fine. |
EMAIL_REPLY_TO |
unset | Reply-To on every message. If unusable, mail goes without it and a warning is logged. |
EMAIL_CAMPAIGNS |
allowed |
0/false/no/off refuses newsletter campaigns (for a one-to-one-only mailbox). |
EMAIL_WEBHOOK_URL |
unset | Endpoint for the webhook transport. |
EMAIL_WEBHOOK_SECRET |
unset | Adds an X-AstroBaaS-Signature header to webhook-transport requests. |
SMTP_HOST |
unset | Mail server host. |
SMTP_PORT |
587 |
Use 465 with SMTP_SECURE=1. |
SMTP_SECURE |
0 |
1 = implicit TLS from the first byte (port 465). Otherwise STARTTLS. |
SMTP_USER / SMTP_PASS
|
unset | Credentials. Single-quote SMTP_PASS in env files: unquoted, systemd drops backslashes and Node's loader stops at #. |
SMTP_TIMEOUT_MS |
15000 |
Per network wait (connect, TLS, each reply) before the message is sent. One attempt is capped at 3x this. |
SMTP_DATA_TIMEOUT_MS |
600000 |
Wait for the server's reply after the message body; clamped to 1000–1800000. A message whose reply never arrives is not resent and is logged as "outcome unknown". |
SMTP_RETRIES |
2 |
0–2 retries, for transient failures before the message is sent only. |
SMTP_HELO_NAME |
machine FQDN, else EMAIL_FROM's domain |
Name given in EHLO. |
SMTP_ALLOW_INSECURE_AUTH |
0 |
1 sends credentials without TLS. Only for a relay on your own network. |
SMTP_ALLOW_SELF_SIGNED |
0 |
1 accepts an unverifiable certificate. Only for a relay on your own network. |
SMTP2GO_API_KEY / SMTP2GO_SENDER
|
unset | Used by the bundled SMTP2GO plugin, once activated under Admin → Plugins. |
Test with npm run mail:test -- you@example.com (in a checkout) or node dist/mail-test.mjs you@example.com (in a built release). Full reference: Email and docs/EMAIL.md.
Nothing is enabled by default. A provider is offered to buyers only when it is listed in PAYMENTS_ENABLED and every credential it needs is set; a half-configured provider is reported as misconfigured in the admin. The manual methods (bank transfer, cash on delivery) need no credentials and are not controlled by these variables.
| Variable | Default | What it does |
|---|---|---|
PAYMENTS_ENABLED |
unset | Comma-separated provider ids: stripe, paypal, klarna. |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
|
unset | Stripe credentials. |
PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID
|
unset | PayPal credentials. |
PAYPAL_ENV |
sandbox |
sandbox or live. |
KLARNA_USERNAME, KLARNA_PASSWORD
|
unset | Klarna credentials. |
KLARNA_REGION |
eu |
eu, na or oc. |
KLARNA_ENV |
playground |
playground or live. |
KLARNA_COUNTRY, KLARNA_LOCALE
|
DE, en-DE
|
Purchase country and locale. |
Each provider's webhook endpoint is /api/payments/webhook/<id>. See Payments and PAYMENTS.md.
| Variable | Default | What it does |
|---|---|---|
SITE_LOCALES |
unset (single language) | Comma-separated locales, for example en,de,fr. Unset means no locale prefixes and no locale UI. |
SITE_DEFAULT_LOCALE |
first entry of SITE_LOCALES
|
The unprefixed default locale. |
WITHDRAWAL_VERIFIED_LOCALES |
unset | Locales whose withdrawal-notice text you have checked against the official EU text. Until a locale is listed, requests for it get the English text with locale_fell_back: true. |
See Internationalisation.
| Variable | Default | What it does |
|---|---|---|
REVISIONS_KEEP |
20 |
Revisions kept per post (hard cap 500). |
REVISIONS_DISABLED |
0 |
1 turns off revision capture and the autosave endpoint. |
SCHEDULER_INTERVAL_MS |
60000 |
How often the background worker sweeps (scheduled posts, abandoned orders, newsletters, off-site backup). Floor 100 ms. |
SCHEDULER_DISABLED |
off |
1 turns the worker off. |
SCHEDULER_LEASE_TTL_MS |
3 x interval | With several processes, how long a dead leader blocks the sweeps. Floor: 2 x interval + 1 s. |
SCHEDULER_LEASE |
on |
0 makes every process sweep. Never set it with more than one process. |
STOCK_WAITLIST_BATCH_SIZE |
50 |
Back-in-stock notices sent per sweep (1–5000). A setting of the same purpose overrides it. |
WEBHOOK_RETRY_DELAYS_MS |
30000,120000,600000 |
Backoff between retries of a failed outgoing webhook delivery. The count is the number of retries. |
| Variable | Default | What it does |
|---|---|---|
ASTROBAAS_PLUGINS |
unset | Comma-separated module specifiers of compiled plugins installed outside the repository. A module that fails to load, or a plugin active in the database with no implementation, is reported loudly on boot. |
ASTROBAAS_PLUGINS_ACTIVATE |
unset | Plugin ids to switch on automatically. An operator decision: plugins cannot activate themselves. |
ASTROBAAS_THEMES |
unset | Comma-separated module specifiers of themes installed outside the repository. |
PLUGIN_REGISTRY_URL |
the AstroBaaS example registry | JSON index of declarative plugin manifests browsable from the admin. Each entry's SHA-256 is verified before install. |
PLUGIN_REGISTRY_DISABLED |
0 |
1 disables registry browsing; manifest upload still works. |
ASSISTANT_API_KEY |
unset | API key for the admin's AI assistant. Takes precedence over a key saved in settings, which is stored in the database in plain text. |
| Variable | Default | What it does |
|---|---|---|
METRICS_ENABLED |
off |
1 exposes Prometheus metrics at /metrics (404 otherwise). Keep it off the public network. |
LOG_REQUESTS |
off |
1 writes one JSON log line per request (method, path, status, ms, ip). |
PROFILE_REQUESTS |
off |
1 records per-request timing spans, shown on the admin operations screen (last 100 requests). |
SHUTDOWN_TIMEOUT_MS |
25000 (Docker image: 8000) |
Total graceful-shutdown budget on SIGTERM/SIGINT. Must be shorter than your supervisor's stop timeout. |
GRACEFUL_SHUTDOWN |
on |
0 exits immediately on the signal, cutting in-flight requests. |
| Variable | Default | What it does |
|---|---|---|
MAINTENANCE_MODE |
off |
1 answers 503 with Retry-After to the public site, while /admin, sign-in, health checks and payment webhooks stay reachable. |
MAINTENANCE_MESSAGE |
unset | Message shown on the maintenance page. |
MAINTENANCE_UNTIL |
unset | Expected end (ISO 8601); used for Retry-After. |
SITE_TITLE |
This site |
Title used by npm run build:maintenance-page for the static page. |
All four of endpoint, bucket, key id and secret are required; with any missing, off-site backup is off.
| Variable | Default | What it does |
|---|---|---|
BACKUP_S3_ENDPOINT |
unset | S3-compatible endpoint (https). |
BACKUP_S3_BUCKET |
unset | Bucket name. |
BACKUP_S3_KEY_ID / BACKUP_S3_SECRET
|
unset | Credentials. Environment only, never settings. |
BACKUP_S3_REGION |
us-east-1 |
Region the endpoint expects. |
BACKUP_S3_PREFIX |
astrobaas |
Key prefix, so one bucket can hold several sites. |
BACKUP_S3_PATH_STYLE |
off |
1 for path-style URLs (MinIO and most self-hosted gateways). |
BACKUP_S3_ALLOW_HTTP |
off |
1 permits plain http, for a MinIO on your own network only. |
BACKUP_EVERY_HOURS |
24 |
Backup interval (maximum 720). |
BACKUP_KEEP |
14 |
Archives kept (maximum 365). Retention runs only after a successful upload. |
BACKUP_ATTEMPT_TIMEOUT_MS |
3600000 |
How long an unfinished attempt blocks a new one. |
Remote libsql:// databases cannot be archived this way; the backup refuses. See Backups and storage.
These are read by the astrobaas CLI and the astrobaas-mcp server on the machine that calls a running install, not by the server itself.
| Variable | Default | What it does |
|---|---|---|
ASTROBAAS_URL (alias ASTROBAAS_BASE) |
http://localhost:4321 |
Base URL of the install. |
ASTROBAAS_KEY |
unset | API key sent as a bearer token. |
ASTROBAAS_SOURCE_KEY, ASTROBAAS_TARGET_KEY
|
ASTROBAAS_KEY |
Separate keys for astrobaas clone. |
NO_COLOR |
unset | Disables coloured CLI output. |
See SDK, CLI and MCP.
Some configuration lives in Admin → Settings rather than the environment, because it must be changeable without a restart or rebuild.
Where the site lives. Used for canonical tags, sitemap.xml, rss.xml, robots.txt and structured data, and for the return URLs payment providers send buyers back to. Precedence: this setting, then the build-time SITE_URL, then the request's own origin. On a headless install this should be the storefront's address.
Where this CMS itself answers: the origin that /uploads/... and /api are joined to when the API publishes absolute media URLs. A coupled install (the CMS serves the public site) can leave it empty. A headless install, or anything calling the API server-to-server, should set it, for example https://cms.example.com.
When it is empty, the media base is guessed: from the request's origin if that is publicly reachable, then from site_url, then from the build-time SITE_URL. The deep health check reports an empty value as a public_site_url warning. Check what the API publishes with:
curl -s https://cms.example.com/api/products?limit=1 | jq -r .meta.media_baseAn http:// answer on an HTTPS site usually means TRUST_PROXY=1 is missing or the proxy does not send X-Forwarded-Proto.
| Setting | Where | Why it matters |
|---|---|---|
Discourage search engines (discourage_indexing) |
Settings → Reading | Hides the site from search. STAGING=1 forces it on regardless. |
| Maintenance mode | Settings → Maintenance mode | A planned window in which staff still see the real site. Needs a working database; for emergencies use MAINTENANCE_MODE. |
Abandoned orders (orders_abandon_enabled, orders_abandon_after_days) |
Settings | Cancels unpaid orders and returns stock (default on, 3 days, clamped 1–90). |
Payment hold (orders_payment_hold_minutes) |
Settings | Unpaid online-payment orders are released after this time (default 120 minutes). |
AstroBaaS is free software under GPL-3.0-or-later. This wiki describes the latest release; the repository and its CHANGELOG are the final word. Found a mistake here? Open an issue.
Getting started
Using the CMS
Building with it
Extending
Commerce and email
Operating
Project