Security hardening
A checklist for running a NextCRM instance on the internet, with the behaviours worth knowing.
Checklist
- Strong
POSTGRES_PASSWORDandMINIO_ROOT_PASSWORD, not the defaultchangeme. -
BETTER_AUTH_SECRETandEMAIL_ENCRYPTION_KEYincluded in your backup plan: either set by you and stored outside the repository, or generated by the Docker entrypoint and kept in theapp_datavolume. - HTTPS in front of the app;
BETTER_AUTH_URLandNEXT_PUBLIC_APP_URLset to thehttps://URL. - Postgres, the MinIO console and the Inngest dashboard not reachable from the internet.
-
INNGEST_DEVnot set to1, so requests to/api/inngestmust be signed withINNGEST_SIGNING_KEY(the compose files set0; see Background jobs). -
NODE_ENV=production(the Docker image sets it). - As few
adminusers as possible; everyone elsemanageroruser. -
MAIL_ALLOW_PRIVATE_HOSTSandPLUGIN_HTTP_ALLOW_PRIVATE_HOSTSleft unset unless you need them. - Awareness that uploaded documents, avatars and images are readable by anyone who has the link (random UUID keys, anonymous read on the upload folders); invoices are private (see File storage behind a proxy).
Secrets
.envis listed in.gitignore. Do not commit it, and do not put secrets into the compose files.- Secrets the Docker entrypoint generates are stored in the
app_datavolume (/app/data/secrets, readable only by the app user). Theinngestservice mounts that volume read-only to get its keys. EMAIL_ENCRYPTION_KEYencrypts stored AI keys, the Resend key, mailbox credentials, calendar tokens, Calendly settings and plugin secrets with AES-256-GCM. Anyone with the database and this key can read them.- AI keys saved under LLM Keys and the Resend key under Services are shown only by their last four characters. A Resend key saved by an older version as plain text is encrypted the next time it is read.
Sign-up and user status
Anyone who can reach the sign-in page can request a code for a new address, which creates a PENDING account. Pending and inactive users are redirected away from the app's pages, and every server action, API route and the MCP server treat them as signed out.
Deactivating a user ends all of their sessions and revokes all of their API tokens at once.
If sign-ups from strangers are a concern, restrict access to the instance at the network level (VPN, IP allowlist or an authenticating proxy).
BetterAuth's built-in admin endpoints (/api/auth/admin/*) are disabled for every role. User management goes through Administration → Users.
API tokens
- Personal tokens for the MCP server start with
nxtc__, are stored as SHA-256 hashes and act with the role of their owner. - A token can have an expiry date; at most 10 active tokens per user.
- The owner can revoke a token in their profile; an admin can revoke all tokens of a user from Administration → Users. Deactivating a user revokes their tokens.
- The MCP server rejects tokens of users who are not
ACTIVE.
Inbound web-to-lead token
NEXTCRM_TOKEN is one shared secret for the inbound endpoints /api/crm/leads/create-lead-from-web and /api/crm/contacts/create-from-remote. Anyone who has it can create leads and contacts. Leave it unset if you do not use these endpoints, and rotate it if it leaks.
Test helpers
Outside NODE_ENV=production, NextCRM enables a BetterAuth test plugin and the endpoint /api/auth/test-otp, which returns login codes for any email. Never run an internet-facing instance in development mode.
Server-side requests
User mailboxes (IMAP/SMTP) and plugin HTTP calls go through guards that refuse private and internal IP addresses, which blocks requests into your internal network. The two opt-outs are MAIL_ALLOW_PRIVATE_HOSTS=true and PLUGIN_HTTP_ALLOW_PRIVATE_HOSTS=true.
Audit
CRM record changes and plugin administration are written to the audit log. Records are soft-deleted and can be restored there.