Skip to content

Configuration

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

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.

How configuration is read

  • 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.ts and baked into the bundle. See Build-time variables.
  • Nothing reads .env automatically in production. npm start does not load it. Use node --env-file=.env ./dist/server/entry.mjs, systemd's EnvironmentFile=, or your container platform's environment. In development, npm run dev does not put .env into process.env either; use node scripts/dev.mjs, which loads .env.local then .env and runs astro 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 accept on/yes. Where a flag is on by default, 0, false, no or off turns it off. When in doubt, use 1 and 0.

Build-time variables

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.

Core and authentication

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.

Bootstrap admin and first boot

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.

Site, URLs and staging

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.

Database

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.

Uploads and media

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.

Proxy, CORS and security

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.

Rate limits and caching

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.

Email

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.

Payments

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.

Internationalisation and legal texts

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.

Content, scheduler and webhooks

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.

Plugins, themes and the assistant

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.

See Plugins and Themes.

Observability and shutdown

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.

Maintenance mode

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.

See Staging and maintenance.

Off-site backup

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.

CLI and MCP client

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.

Runtime settings that matter

Some configuration lives in Admin → Settings rather than the environment, because it must be changeable without a restart or rebuild.

Site URL (site_url)

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.

Address of this CMS (public_site_url)

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_base

An http:// answer on an HTTPS site usually means TRUST_PROXY=1 is missing or the proxy does not send X-Forwarded-Proto.

Other settings worth knowing

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).

See also

Clone this wiki locally