NextCRM

Security hardening

A checklist for running a NextCRM instance on the internet, with the behaviours worth knowing.

Checklist

  • Strong POSTGRES_PASSWORD and MINIO_ROOT_PASSWORD, not the default changeme.
  • BETTER_AUTH_SECRET and EMAIL_ENCRYPTION_KEY included in your backup plan: either set by you and stored outside the repository, or generated by the Docker entrypoint and kept in the app_data volume.
  • HTTPS in front of the app; BETTER_AUTH_URL and NEXT_PUBLIC_APP_URL set to the https:// URL.
  • Postgres, the MinIO console and the Inngest dashboard not reachable from the internet.
  • INNGEST_DEV not set to 1, so requests to /api/inngest must be signed with INNGEST_SIGNING_KEY (the compose files set 0; see Background jobs).
  • NODE_ENV=production (the Docker image sets it).
  • As few admin users as possible; everyone else manager or user.
  • MAIL_ALLOW_PRIVATE_HOSTS and PLUGIN_HTTP_ALLOW_PRIVATE_HOSTS left 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

  • .env is 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_data volume (/app/data/secrets, readable only by the app user). The inngest service mounts that volume read-only to get its keys.
  • EMAIL_ENCRYPTION_KEY encrypts 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.

On this page