Hello
I’m building a multi-tenant automation platform on top of self-hosted n8n where each client has:
Their own API credentials/tokens
Their own WhatsApp/WAHA instance
Separate databases/services
Right now, all workflows run on shared infrastructure, but I want to make sure tenant credentials are isolated properly and safely at scale.
My concerns are:
Preventing one tenant from accidentally accessing another tenant’s credentials
Managing credential rotation across many tenants
Avoiding hardcoded credentials in workflows
Safely handling dynamic credential injection
Current architecture is roughly:
Tenant Request → Shared Workflow → Load Tenant Config → Execute
I’m considering:
Separate n8n credentials per tenant
External secret manager (Vault, AWS Secrets Manager, etc.)
Encrypting tenant configs in DB
Dedicated workers for high-security tenants
For people running multi-tenant n8n systems in production:
What’s the safest and most maintainable credential isolation strategy?
Are you storing credentials inside n8n, external secret stores, or a hybrid setup?
Any patterns to avoid when scaling to many tenants?
Describe the pro
What is the error message (if any)?
Please share your workflow
(Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.)
I would avoid treating this as one shared workflow with dynamic credentials as the default pattern. That is the path that usually becomes hard to reason about later. A safer shape is: - one tenant_id is created at the edge and passed through the whole run - no credential value ever moves through normal item data, execution logs, or webhook payloads - tenant config in your DB contains references/secret IDs, not the secret material itself - a small server-side broker resolves tenant_id → allowed secret at execution time - every resolved secret access is logged with tenant_id, workflow_id, credential_id, timestamp, and actor/system - rotation is tested per tenant with a small healthcheck workflow before switching traffic For n8n specifically, I would keep normal n8n credentials when the tenant count is small and the operational boundary is simple. Once tenants grow, or if tenants have different security requirements, I would move the actual secret material into Vault / AWS Secrets Manager / similar and let n8n receive only what it needs for that run. The pattern I would avoid is dynamic credential injection from workflow input. It is flexible, but it makes it too easy for a bad mapping, bad tenant_id, or copied workflow to cross tenant boundaries. If some clients are higher risk, dedicated workers or even dedicated n8n instances are easier to audit than a clever shared setup. It costs more operationally, but the failure mode is much cleaner.
Welcome @Gabby_Timothy to our community! I’m Jay and I am a n8n verified creator.
Building on clawilab’s pattern - for the dynamic injection part in n8n, a practical approach: store each tenant’s credentials in your DB (encrypted), then at the start of each workflow run use an HTTP Request node to fetch the relevant credentials from your own internal API using the tenant_id passed in from the trigger. Reference the returned values in subsequent nodes via expressions like {{$node['FetchCredentials'].json.apiKey}}.
This keeps all secrets entirely out of n8n’s credential system - n8n only needs auth for your own internal broker API. You get rotation in one place and zero risk of cross-tenant bleed. The tradeoff is one extra API call per workflow run, but for a multi-tenant setup the isolation is worth it.
clawilab’s shape is the right one, especially “config holds secret references, not the material” and “no credential value moves through item data or execution logs.” That second rule is the one most multi-tenant setups break first, and it’s the hardest to walk back once you have live tenants.
On your store-in-n8n vs external vs hybrid question: hybrid, but where you draw the line matters. Keep n8n’s own credentials for the low-risk shared stuff. For per-tenant client tokens, don’t let the secret reach the workflow at all. That’s the part dynamic injection from workflow input gets wrong - a bad tenant_id or a copied workflow can’t leak a value the run never held.
There’s a cleaner version of clawilab’s broker worth considering: instead of resolving tenant_id → secret and handing it back into the run, put the broker in the request path. The workflow calls a normal endpoint, the broker injects the right tenant’s credential on the outbound call, and the run only ever sees a reference. You get the per-tenant scoping and the per-access audit trail as a side effect, and rotation becomes a config change on the broker instead of a redeploy of every workflow.
Hi @Gabby_Timothy Most production multi-tenant setups use a hybrid approach:Shared workflows + isolated tenant credentials/config
Common best practice
Keep workflow logic shared
Store tenant credentials separately
Load credentials/config dynamically using tenant_id
Many teams use:
n8n credentials for simpler setups
External secret managers (Vault, AWS Secrets Manager, etc.) for larger/high-security systems
Just make sure to Avoid:Hardcoding credentials in workflows
Storing all tenant secrets in one shared config object
Letting workflows access credentials from other tenants
For better isolation try to Encrypt tenant configs in DB
Use role-based access controls
Give high-volume/high-security tenants dedicated workers or environments
The pattern I’ve found works well in practice: store an AES-encrypted config object per tenant in Postgres, with a master encryption key set only as an environment variable on the n8n host. At workflow start, a Set node fetches and decrypts the tenant config via a Code node using the env key. This way the actual secrets never live in workflow JSON, execution logs, or the n8n credential vault - only the encrypted blob in DB and the decryption key on the host. Rotation means updating the DB record, not redeploying workflows.