Security & production checklist
Indelible fails closed in production: it refuses to boot with development secrets and defaults egress protections on. This page covers the settings that matter for a safe self-hosted deployment.
Secrets
Section titled “Secrets”Generate strong, unique secrets and keep them out of version control:
openssl rand -base64 32 # JWT_SECRET: signs short-lived access tokensopenssl rand -base64 32 # CSRF_SECRET: signs CSRF tokensopenssl rand -base64 32 # AUTH_CREDENTIAL_KEY: encrypts stored integration tokensJWT_SECRET/CSRF_SECRET: required in production; the API will not start with the development defaults. To rotateJWT_SECRET, deploy the new value in a low-traffic window; outstanding access tokens become invalid and clients transparently re-authenticate via the refresh cookie.AUTH_CREDENTIAL_KEY: required in production whenever an integration (e.g. Notion) is configured. It encrypts the OAuth tokens Indelible stores on a user’s behalf. Set the same value onind-apiandind-worker.
Accounts
Section titled “Accounts”Registration is closed by default:
AUTH_ALLOW_SIGNUPS: 'false'The first account on an empty instance can always be created. Set
AUTH_ALLOW_SIGNUPS=true temporarily only when you want additional people to
create accounts, then return it to false.
Network exposure
Section titled “Network exposure”- TLS: terminate HTTPS at a reverse proxy (Caddy, nginx, Traefik) in front of
the API. Set
IND_BASE_URL,FRONTEND_URL, andCORS_ORIGINSto your realhttps://origins. The mobile apps default to HTTPS and show an explicit unencrypted-connection warning before they’ll talk to a plain-http://server; see Use the mobile apps. TRUSTED_PROXIES: when running behind a proxy or CDN, set this to the proxy’s IPs/CIDRs so rate limiting and audit logs see the real client IP. Leave it empty when the API is exposed directly, otherwise a client can spoofX-Forwarded-Forto evade auth rate limits.CORS_ORIGINS: lock this to the exact origin(s) serving your web app. Never use a wildcard.
Server-side fetch protections
Section titled “Server-side fetch protections”Indelible fetches user-supplied URLs (article capture, feeds, the renderer, AI providers, webhook delivery). Two guards keep those fetches from reaching your internal network:
EGRESS_ALLOW_PRIVATE_TARGETS: defaults off in production. It blocks fetches to private, loopback, link-local, and cloud-metadata addresses. Only set ittrueif you must reach a local renderer/feed/AI endpoint, and network-isolate those services if you do. Set it consistently onind-api,ind-worker, andind-renderer.WEBHOOKS_ALLOW_PRIVATE_TARGETS: defaults off; only honored in addition to the egress guard. Leave off unless you deliberately deliver webhooks to a private endpoint.
Email ingestion
Section titled “Email ingestion”If you enable inbound email (EMAIL_INGEST_PROVIDER=resend), set
EMAIL_INGEST_WEBHOOK_SECRET so Indelible can verify that inbound webhooks really
came from your provider, and keep RESEND_API_KEY secret.
Object storage
Section titled “Object storage”- Keep the bucket private. By default (
ASSET_SERVING_MODE=passthrough) assets are streamed through the API, so the object store never needs to be reachable from a browser at all. SetASSET_COOKIE_SECRET(64-character hex); the API refuses to start in production without it in that mode. ASSET_SERVING_MODE=presignedmakes asset downloads redirect to short-lived signed URLs atS3_ENDPOINT, offloading download bandwidth to the object store. Only use it when that endpoint is reachable from your users’ browsers and terminates TLS (for example real AWS S3). Uploads go through the API either way.- Restrict the S3 credentials to the single Indelible bucket.
Backups
Section titled “Backups”Back up two things together:
- PostgreSQL: all metadata, accounts, highlights, and reading state.
- Object storage: the archived content itself (the bucket).
A database restore without its matching bucket (or vice versa) leaves dangling references. Snapshot both on the same schedule.
Production checklist
Section titled “Production checklist”-
IND_ENV=production - Strong
JWT_SECRETandCSRF_SECRET(not the dev defaults) -
AUTH_CREDENTIAL_KEYset on api + worker if any integration is configured -
IND_BASE_URL,FRONTEND_URL,CORS_ORIGINSset to your real HTTPS origins - TLS terminated at the proxy;
TRUSTED_PROXIESset to the proxy ranges -
AUTH_ALLOW_SIGNUPS=falseunless intentionally onboarding additional accounts -
EGRESS_ALLOW_PRIVATE_TARGETSleft off (or services network-isolated if on) - Object storage bucket private; S3 credentials scoped to that bucket
-
EMAIL_INGEST_WEBHOOK_SECRETset if inbound email is enabled - PostgreSQL + object-storage backups scheduled together