What n8n security means once real credentials are in it
A self-hosted n8n instance that only moves test data between demo apps does not need much of a security posture. One that holds your Shopify Admin API token, your payment processor’s secret key and a customer database credential is a different animal: it’s now a single machine that, if compromised, gives an attacker roughly the same reach your finance and support teams have. n8n security, on an instance handling real store data, is not really about the workflow builder itself. It comes down to four things underneath it: how credentials are stored at rest, how reachable the webhook endpoint is from the open internet, who can log in, and what gets left behind in the execution log after a workflow runs. Get those four right and the rest of the hardening work is routine maintenance. Get the encryption key wrong, and the rest doesn’t matter much.
Who this guide is for, and who should sit this one out
This guide is written for the team running n8n against production systems: order webhooks from Shopify, a payment gateway’s API, a customer data platform, maybe a warehouse management system. If your instance only touches internal spreadsheets and Slack notifications, most of this still applies, but the stakes are lower and you can move through it faster. If you’re running n8n as a personal experiment with no real credentials attached yet, none of this is urgent this week. And if you’re below the point where a paid subscription platform and a dedicated person responsible for infrastructure make sense, meaning roughly under $3M in revenue, the honest answer is that a hosted automation tool, with someone else on the hook for patching, is probably the better trade than taking on a self-hosted instance at all. This guide assumes you’re past that point: on Shopify Plus or an equivalent paid platform, with enough transaction volume that a leaked credential is a real incident and not a hypothetical one.
What you need before you start
Before touching any settings, get four things in place. Admin access to the host running n8n, whether that’s a VM, a container platform or a managed server, with the ability to set environment variables and restart the process. A secrets manager, or at minimum an encrypted storage location separate from your regular database backups, to hold the encryption key and any credentials you rotate during this work. A reverse proxy or load balancer capable of TLS termination and path-based routing, if you don’t already have one in front of n8n. And an inventory of every credential currently stored inside the instance, so you know what’s actually exposed while you work through the rest of this: which API tokens, which OAuth connections, which database logins, and which of those touch payment or customer data directly.
How n8n stores your credentials, and why the encryption key matters most
n8n encrypts credential values before writing them to its database, using a key it either generates itself on first start or reads from an environment variable you set. If you never set that variable, n8n creates the key automatically and stores it in a local config directory on the host. That’s fine for a laptop running test workflows. It’s a genuine risk for a production instance, because the key then exists in exactly one place, on one disk, usually never backed up on its own.
A lost encryption key doesn’t just cause an inconvenience. It makes every credential n8n has encrypted unreadable at once, not individually recoverable. You’d need to re-enter every API token, every OAuth connection and every database login stored in the instance, on every workflow that uses one, from scratch. There’s no partial recovery: the key either decrypts everything it was used to encrypt, or it decrypts nothing.
Rotating the key deliberately has the identical effect, which is worth saying plainly because it surprises people who expect key rotation to work the way it does with, say, an API token that has an overlap window. It doesn’t here. Change the encryption key and every credential encrypted under the old one stops working the moment you restart with the new one. If you do decide to rotate, and there are good reasons to (a suspected compromise, an employee departure who had access to the key, a migration to a proper secrets manager), treat it as a scheduled maintenance window where you budget time to re-authenticate every connected service in one sitting, not as something you slot in between other tasks.
Set and back up the encryption key properly
Generate the key explicitly rather than letting the default behaviour create one for you. Use a proper random generator to produce a long value, at least on the order of 32 bytes, rather than typing something memorable; a memorable key is a guessable one, and there’s no reason for a human to ever need to type this value from memory. Set it as the encryption key environment variable before the instance’s first start, or before a deliberate rotation, using whatever mechanism your host supports for injecting environment variables at process start, whether that’s a container orchestrator’s secrets feature, a .env file with restricted permissions, or your platform’s own secrets manager integration.
Store a second copy somewhere your incident response process can reach even if the host itself is unavailable: a password manager with restricted access, or a dedicated secrets vault, not a text file sitting in the same directory as your workflow exports. This matters more than it sounds like it should. The most common failure here isn’t a sophisticated attack; it’s a database backup and the encryption key ending up in the same storage bucket or the same git repository, because whoever set up backups treated the key as just another config value. An attacker who gets both gets a decrypted view of everything. An attacker who gets only the encrypted database gets nothing usable. Keep them apart, and audit where each one actually lives at least once, because “we back it up somewhere” is not the same as knowing where.
Take the webhook endpoint off the open internet
n8n’s webhook nodes create an endpoint reachable at a path under your instance’s public URL, and for most production workflows, that endpoint being public is the entire point of it: Shopify, your payment gateway, or a customer data platform push events to it as things happen in the real world, which is exactly what a one-way integration needs. The fix here isn’t to hide the endpoint. It’s to control what an unauthenticated request to it can actually do.
Start with the network path. Run n8n behind a reverse proxy that terminates TLS and only forwards the specific paths you actually use, the webhook path and the editor path, rather than exposing the whole application surface directly to the internet. This alone rules out a lot of casual scanning traffic that would otherwise hit endpoints you never intended to be public.
Next, verify signatures. Most vendors that push webhooks to a third-party system sign the request body with a shared secret, precisely so the receiving system can confirm the request genuinely came from them and hasn’t been tampered with in transit. Enable that verification on every webhook node that supports it, so a request without a valid signature never reaches your workflow’s actual logic, whether that’s writing to your order database or triggering a refund. A webhook node that accepts anything sent to its URL is trusting the URL’s obscurity as its only defence, and a URL is not a secret once it’s been used in a vendor dashboard, a support ticket, or a Slack message.
Where the vendor publishes source IP ranges for their webhook infrastructure, add an allowlist at the proxy layer as a second control. Shopify and several payment gateways publish these ranges; check the current version on the vendor’s own documentation rather than trusting a list you find elsewhere, since these ranges do change. Rate-limit the endpoint too, so a retry storm from a misbehaving integration, or a scan, doesn’t queue thousands of executions and choke the instance for everyone else using it.
Finally, be disciplined about test versus production paths. n8n’s test webhook URLs behave differently from production ones and are genuinely useful while you’re building a workflow, but they’re also easy to leave active after the workflow ships, because nothing forces you to go back and disable them. Get in the habit of confirming a test URL is switched off once a workflow goes live, and never point a real vendor integration at a test path as a long-term arrangement because it happened to work during development.
Replace default login with real authentication
Modern self-hosted n8n supports individual accounts with email and password login and an owner role, which has largely replaced the older approach some earlier deployments used of a single shared login for the whole editor. Exactly which setting controls this, and what the defaults are, has changed across releases, so check your version’s current documentation before assuming anything about what’s active out of the box.
What doesn’t change across versions is the operational discipline. Claim the owner account the moment the instance is stood up; don’t leave a fresh install reachable on the network with its setup wizard uncompleted, even briefly, because that’s a window where anyone who finds the URL can claim ownership themselves. Create individual accounts for each person who needs access, rather than one shared login passed around in a chat message, so that when someone leaves the team you can revoke their access specifically instead of rotating a password everyone else also has to learn.
If your organisation already runs an identity provider and wants single sign-on into n8n, check whether your licence tier includes it before building a workflow around the assumption that it does; self-hosted community deployments and licensed editions differ on this, and the current pricing and licensing page is the place to confirm it, not a general assumption carried over from another tool. Whatever authentication you land on, put the editor UI itself behind the same access boundary, VPN or IP allowlist, as any other internal admin tool your team runs. There’s no reason the interface that can read and edit every stored credential should be reachable from anywhere a browser can reach the internet.
Put a patching cadence on the calendar
Self-hosting means you own the update process; there’s no vendor SRE team pushing a fix to your instance overnight. n8n ships releases on a regular basis, and some of those releases carry fixes to things that matter specifically for security, not just new nodes or UI changes. “If it isn’t broken, don’t touch it” is a reasonable instinct for a lot of software, but it’s the wrong instinct for an instance sitting next to payment and customer data, because a known issue that’s been patched upstream doesn’t stop being exploitable just because nothing has visibly gone wrong yet on your instance.
Set a fixed cadence rather than waiting for a prompt. Checking monthly is a reasonable default for most teams at this size; checking weekly is defensible if your instance is genuinely central to order or payment flow. When a new release lands, read its notes specifically for anything flagged as a security fix, not only the headline features, and treat those with more urgency than a routine version bump.
Before touching production, run the new version against a staging copy: a separate instance with its own database and its own encryption key, populated with a representative set of workflows, so a breaking change in the new release surfaces there first rather than against real orders. This matters more with n8n than with some tools, because node behaviour and configuration options do shift between versions, and a workflow that runs cleanly on the old version can fail silently, or worse, fail loudly mid-checkout, on the new one if you skip staging. If you’ve fallen several versions behind, read the changelog for each minor version you’re jumping across, not just the one you’re landing on, since a breaking change introduced two versions ago still applies to you.
Patch the host underneath n8n on the same schedule: the operating system, and if you’re running it in a container, the base image the container is built from. A perfectly patched n8n application sitting on an unpatched host operating system, or an old base image with known vulnerabilities baked into it, is still an exposed instance.
Decide what your execution log is allowed to keep
Every workflow execution in n8n records, by default, the data that passed through each node during that run, precisely so you can open it later and see what happened when something breaks. That’s genuinely useful for debugging. It’s also, on an instance wired into your store, the same log that now contains whatever your webhook received: a customer’s email address and shipping details from an order-created event, a payment token reference and card metadata from a payment event, potentially a full cart’s contents from a checkout workflow. Treat that execution log the same way you’d treat a database with customer PII and payment metadata sitting in it, because functionally, that’s exactly what it is, and it deserves the same retention discipline you’d apply to any other system holding that kind of data.
Configure execution data retention so that old runs are pruned automatically after a defined window, rather than accumulating indefinitely with no expiry and no one responsible for them. Exactly which setting controls this, and whether it lives in an environment variable or the instance’s own settings UI, has moved between releases, so check your current version’s documentation for the specific mechanism rather than assuming a name from an older setup guide.
Beyond retention, be selective about what gets saved in the first place. Not every workflow needs its successful execution data kept at all; turn that off for workflows you’ve already validated and don’t actively debug, and keep it on only for the ones you’re still actively working through. Where a specific node handles a payment token or another sensitive value, check whether that node’s output includes the full response body or a redacted one, and prefer a credential reference over passing the raw value through the workflow as ordinary data wherever the integration supports it. Finally, restrict who can view the execution list itself. It should sit behind the same access tier as your CRM or your order management system, not be visible to everyone with general engineering access to the instance, because the execution list is, in practice, a searchable log of customer and payment activity even though nobody built it to be one on purpose.
The setting most teams get wrong
Two things, and they’re related. The first is the encryption key: teams stand up n8n, connect their first few credentials, and never come back to explicitly set the key, so it sits auto-generated on a single disk with no backup, for months, until someone asks during a security review and nobody can say for certain where it lives or whether it’s backed up. The second, and the one that gets missed even by teams who do handle the key properly, is execution data retention. It’s treated as inert debug output, something you’d only look at if a workflow breaks, and it’s left on its default settings indefinitely because nothing about a growing execution log is visibly broken. Nobody decides to keep months of customer addresses and payment metadata sitting in a database with no formal owner; it just happens, one un-pruned execution at a time, because retention was never a decision anyone made on purpose.
How to verify your instance is actually locked down
Work through this as a checklist rather than taking any of the previous sections on faith. Confirm the encryption key was set explicitly rather than auto-generated, and confirm a copy exists somewhere outside your primary database backup, in a location someone other than you could find in an emergency. Send a request to the webhook URL from outside your network without a valid signature and confirm it’s rejected at the workflow level rather than silently queued and executed. Try logging into the editor UI with no credentials shared over chat, confirming each team member has their own account and the original setup account has been claimed. Check the running version against the project’s current release notes and note, honestly, how many releases behind you are and what’s in the ones you’ve skipped. Open the execution list for every workflow that touches customer or payment data and confirm you can state, for each one, how long its execution data is retained and why that window was chosen, not just that it exists.
Why this is an automation-governance problem, not just an infrastructure one
Locking down the instance is necessary work, but it doesn’t answer a different question: what is the automation itself allowed to decide on its own, once the platform underneath it is secure? A workflow that correctly rejects an unsigned webhook request can still auto-tag a customer as fraudulent from one ambiguous signal, or retry a failed payment call in a way that double-charges a card, and neither of those is something an encryption key or a firewall rule fixes. That’s a question of who owns what the automation is trusted to act on without a human checking first, which is a design decision, not a security setting. It’s the problem Pointerflow’s AI agents and automation work is built to own alongside the platform hardening covered here, so the instance isn’t just secure, it’s trustworthy with what it’s allowed to do.
Sources
- No external figures are quoted in this article. It’s written from n8n’s documented product behaviour (credential encryption, webhook handling, user management and execution data) and general self-hosted infrastructure security practice.