How do you secure self-hosted n8n on Docker?
Self-hosted n8n holds the keys to every system it automates, so secure it like a server full of company passwords. The minimum is ports 80 and 443 only, behind a reverse proxy with HTTPS, an owner account with 2FA, an encryption key backed up off the server, and authentication on every webhook. At the maximum level the editor is reachable only over VPN, and the only traffic from the internet is authenticated webhooks through a gateway with a WAF.
- Updated
- Oct 22, 2026
- Published
- Oct 22, 2026
- 12 min read
- Author
- Romuald Członkowski
Which security level do you need?
Not every instance needs a VPN and a WAF gateway. This is the split I use on client projects:
| Level | For whom | What it covers |
|---|---|---|
| Baseline | every instance, including test ones | ports 80 and 443, HTTPS, owner account with 2FA, backed-up encryption key, authenticated webhooks, updates and backups |
| Business | n8n with customer data, invoices, CRM access | baseline plus a hardened server with fail2ban, public API off, SSRF protection, isolated Code node, regular audits |
| Maximum | n8n with access to mail, file storage and internal systems, or in a large organisation | business plus a VPN-only editor, a WAF gateway in front of webhooks, an egress allowlist and a separate instance for external integrations |
The sections below go from baseline to maximum. Settings I skip at the lower levels are covered where they start to make sense.
Is n8n secure, and what are you protecting?
n8n's defaults are sensible. What decides security is the configuration, because n8n is a more attractive target than a typical web app. Someone who takes over an instance gets:
- Credentials. API keys and OAuth tokens for the CRM, mail, accounting and databases. One instance holds access to everything it automates.
- Code execution. The Code node runs JavaScript and Python on your server.
- A way into the internal network. An HTTP Request node on the n8n server reaches places the internet cannot: the database on the same Docker network, internal services, cloud metadata addresses.
- Data in execution history. Every execution stores its input and output, often including personal data.
There are three ways in: the editor and its API, webhooks and forms, and the server itself. The checklist below closes them one by one.
Which ports should be open to the internet?
Only 80 and 443, and only for the reverse proxy. In a Docker Compose install, Caddy or another reverse proxy sits in front of n8n, issues the HTTPS certificate and forwards traffic to the n8n container on the private Docker network. Ports 5678 (n8n), 5432 (Postgres) and 6379 (Redis in queue mode) have no host mapping.
Watch out for one trap: a port published by Docker in the ports section bypasses ufw, because Docker inserts its own iptables rules ahead of ufw's. If someone adds 5678:5678 "just for a moment", n8n is reachable from the internet without HTTPS, even though ufw status shows the port as closed. So I keep two layers:
- In the compose file: only the reverse proxy has port mappings.
- In the cloud provider's firewall (Hetzner Cloud Firewall, AWS Security Group, Azure NSG): inbound 80, 443 and SSH only. This firewall sits in front of the server, so Docker cannot bypass it.
The reverse proxy adds security headers: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN and Referrer-Policy.
How do you harden the server under n8n?
The n8n container is only as secure as the machine it runs on. I do the same on every server:
- SSH with keys only. In
/etc/ssh/sshd_configI setPasswordAuthentication noandPermitRootLogin no, and administer from a separate account with sudo. - fail2ban on SSH. After a few failed logins the IP address is banned for a set time. With key-only login an attacker cannot get in anyway, but fail2ban cuts automated scanning and log noise.
- fail2ban on reverse proxy logs, optionally. You can ban addresses that hammer the editor login. Traffic to containers goes through Docker's iptables chains, though, so the fail2ban action has to write rules to the
DOCKER-USERchain, not the defaultINPUT. Otherwise the ban blocks nothing. At the maximum level this point disappears, because the editor is not on the internet. - Automatic OS patches. On Ubuntu and Debian,
unattended-upgradesinstalls security fixes for system packages. It does not touch the n8n image, which I update separately. - Care with the
dockergroup. Anyone in it effectively has root, because they can start a container with the host's whole filesystem mounted. I add only the people who administer the server. - Nothing but n8n. The n8n server does not also host the company website or a database admin panel. Every extra service is another route to the credentials.
What should you do in the first minutes after installing?
Create the owner account before anyone else learns the address. A fresh n8n instance shows the owner setup form, and whoever submits it first owns the instance. An unconfigured n8n exposed to the internet is a race, and bots watch newly issued HTTPS certificates, so a new address does not stay unknown for long.
Right after that:
- 2FA for the owner and every user. In the free Community Edition each user turns it on themselves. Enforcing 2FA across the instance requires a paid plan.
- SMTP. Without mail configured, invite links have to be passed on by hand and users cannot reset their own passwords.
In automated deployments the owner account can be created from environment variables with a password hash (N8N_INSTANCE_OWNER_MANAGED_BY_ENV), and then there is no race at all.
How do you protect the n8n encryption key?
N8N_ENCRYPTION_KEY encrypts every credential stored in the database. It is the instance's most important secret, and three rules are absolute:
- Set it explicitly before the first start. If you don't, n8n generates a random key and stores it in the data volume. Supplying a different key later breaks decryption of every credential.
- Keep a copy off the server, in a password manager or a secrets vault. A database backup without the matching key is useless.
- Never change it by editing
.env. That is not rotation, it is losing every credential. n8n has a separate, one-way key rotation procedure, which you run after a full database backup.
The other secrets follow the same hygiene: each instance gets fresh passwords generated on the server (openssl rand -base64 32), secrets live only in a .env file with 600 permissions, and .env never goes into a repository. I never copy a key or a .env from one client's instance to another.
Which environment variables should you set?
Some settings prevent configuration mistakes behind a reverse proxy, others turn off features you don't use. My starting set:
| Variable | Value | Why |
|---|---|---|
N8N_PROTOCOL, N8N_PROXY_HOPS | https, 1 | n8n knows it sits behind a proxy and trusts the X-Forwarded-* headers |
N8N_SECURE_COOKIE | true | the session cookie only travels over HTTPS |
N8N_WEBHOOK_URL, N8N_EDITOR_BASE_URL | the public HTTPS address | without them n8n hands out webhook and callback URLs on localhost; from n8n 2.35 the older WEBHOOK_URL name is deprecated |
N8N_DIAGNOSTICS_ENABLED | false | turns off anonymous telemetry |
N8N_BLOCK_ENV_ACCESS_IN_NODE | true | nodes and expressions cannot read the container's environment, which holds your secrets |
N8N_PUBLIC_API_DISABLED | true if unused | turns off the public REST API; leave it on if you connect n8n-mcp or AI agents |
N8N_SSRF_PROTECTION_ENABLED | true | stops HTTP nodes from reaching private, loopback and cloud metadata addresses; add the exceptions you need to the allowlist |
N8N_COMMUNITY_PACKAGES_ENABLED | false if unused | community nodes are third-party code with access to your credentials |
N8N_TEMPLATES_ENABLED | false at the maximum level | turns off templates and the related calls to n8n's servers |
EXECUTIONS_DATA_PRUNE, EXECUTIONS_DATA_MAX_AGE | true, e.g. 336 hours | execution history with personal data does not sit in the database forever |
Two traps:
NODES_EXCLUDE. From n8n 2.0 the Execute Command node is blocked by default. SettingNODES_EXCLUDE=[], which looks like "exclude nothing", turns it back on. If you want to block more nodes, list them all in the array, Execute Command included./metrics. WithN8N_METRICS=true, n8n exposes a metrics endpoint without authentication. The reverse proxy forwards everything, so the metrics are public until you block that path in the proxy.
How do you secure the Code node?
The Code node runs code in a task runner. In the default internal mode the runner is a child process of n8n with the same permissions, so it provides no isolation. The n8n docs reserve this mode for instances without sensitive data.
At the business level I switch to external mode: a separate n8nio/runners container runs next to n8n, on the same version. It is also the only way to get Python Code nodes working on the stock image.
The second part is module allowlists. In Python not even import json passes by default, which makes * tempting. On an instance where more than one person edits workflows I never do that, because * includes os, subprocess and socket, which means full access to the system and the network. I add the specific modules the workflows need.
How do you secure webhooks in n8n?
Every production webhook requires authentication. The Webhook node supports Basic Auth, Header Auth and JWT. For system-to-system integrations the simplest is a header with a long random token, stored as a credential rather than hard-coded in the node.
The second layer is the Webhook node's IP(s) Allowlist option. A request from outside the list gets a 403. It makes sense when the sender publishes its IP addresses. Behind a reverse proxy it only works with a correct N8N_PROXY_HOPS, otherwise n8n sees the proxy's address instead of the sender's.
The most important fact in this section: n8n has no setting that enforces authentication on every webhook. Any user with edit rights can build a public, unprotected webhook. At the baseline and business levels the audit catches this: n8n audit (or the audit tool in n8n-mcp) lists unprotected webhooks. I run it regularly, and on business instances on a schedule with an alert to whoever is responsible. At the maximum level the gateway in front of n8n enforces it, as described below.
The same rule applies to forms: an internal form gets Basic Auth in the Form Trigger node.
How do you update n8n and back it up?
Updates. I pin the image version in .env instead of using latest, so an update is a decision and not a side effect of some other change. The n8n docs recommend updating at least once a month. Before each update I read the release notes, and critical security fixes go in faster.
Back up before the update, not after. n8n runs database migrations automatically at startup, and they only run forward. Rolling back after an update therefore takes the old image plus a database backup from before the update.
What goes in the backup. Two things that only make sense together: the encryption key and the data (the SQLite volume or a pg_dump of Postgres). Backups go off the server, and I test a restore at least once, on a clean machine.
How do you secure n8n at the maximum level?
I design this setup for instances that have to be reachable from the internet for webhooks and AI assistants, while running inside a large organisation or close to sensitive data. It is based on an architecture I recently designed for a client.
Two instances instead of one
The instance with access to mail, company file storage and internal systems is not exposed to the internet at all. External integrations (ad platforms, cloud CRM, websites) run on a second, separate instance with no route to that data: it stores no Microsoft 365 or Google Workspace credentials and has no network path to internal resources.
Why the extra complexity? Because the worst case then leaks data the company already keeps with outside vendors, not the board's mailboxes. With a single instance, every webhook flaw is a flaw in access to everything.
Editor over VPN only
The editor, the API and test webhooks are reachable only from the corporate network or over VPN (WireGuard, Tailscale, the corporate VPN), with SSO sign-in where the licence allows it. The alternative is a zero-trust model such as Cloudflare Access, where access goes to a signed-in person rather than to anyone on the network. SSH also runs over the VPN only, with port 22 closed to the internet in the cloud firewall.
Things that are easy to cut off by accident:
- OAuth callbacks. They are opened by the browser of the person connecting the credential, so they work over the VPN.
- n8n-mcp and AI agents need the public API (
/api/v1/). They must run inside the VPN or pass Cloudflare Access with a service token. An agent outside the VPN gets a refused connection, which looks exactly like a wrong instance URL.
The cleanest layout is two subdomains: a public one for webhooks and forms only, and one for the editor, visible only over the VPN. If you must stay on one domain, the reverse proxy lets through only the production paths from the internet. By default these are /webhook/, /webhook-waiting/, /form/ and /form-waiting/, while everything else, including /webhook-test/, /form-test/, /rest/ and /api/v1/, is served to VPN traffic only. The paths can be changed with the N8N_ENDPOINT_* variables, so I check the list against the actual instance's configuration and test the setup before going live.
A WAF gateway in front of webhooks
Since n8n does not enforce webhook authentication itself, a gateway in front of it does: Azure Application Gateway, AWS WAF, Cloudflare or another reverse proxy with rules. The gateway publishes only the production webhook path and rejects every request without the required header or token. An unprotected webhook is then unreachable from the internet, even if someone builds one.
Behind the gateway the business-level layers stay in place: authentication in every Webhook node, an IP allowlist where the sender publishes addresses, and n8n audit on a schedule with an alert to IT.
Forms
An n8n form is opened by a person's browser, and a browser will not send a secret header. A gateway rule of "no token, no entry" therefore blocks every public form. Forms get their own rules:
- An internal form stays behind the VPN, with Basic Auth in the Form Trigger.
- A public form passes the gateway without a token requirement, but with a per-address rate limit and WAF bot rules.
Form pages and the hosted chat also load n8n's static files. With strict path filtering a form may fail to render, so I test it in a browser outside the VPN before locking down the rest.
The MCP endpoint for Claude and ChatGPT
If the team builds or runs workflows from company Claude or ChatGPT accounts, those services connect to the MCP server from the vendor's cloud, not from the employee's computer. The instance's MCP endpoint (/mcp-server/http) then goes through the same gateway, with three restrictions:
- it accepts only users signed in through OAuth,
- optionally only from the IP ranges the AI vendor publishes,
- it exposes only workflows explicitly enabled for MCP.
The variant without a public MCP endpoint: employees use local connectors in Claude Desktop or Codex on their own computers, over the VPN. No AI vendor's cloud then connects to the instance.
Outbound traffic
The reverse proxy only sees inbound traffic. The calls n8n makes to APIs leave the container directly, so limiting them is a job for the cloud firewall or an egress proxy, not for Caddy or Nginx.
An egress allowlist (only the domains of the platforms in use, e.g. Google, Meta, the CRM) limits the damage after a compromise, because the data has nowhere to go. It makes sense on an instance that talks to a handful of known services. On a general-purpose instance with dozens of integrations the list becomes unmaintainable. There, SSRF protection plus turning off unneeded connections, as in n8n's guide to isolating an instance, is enough.
Other maximum-level settings
- Critical n8n security fixes applied within 72 hours of release.
- Community nodes, templates and telemetry turned off.
- A neutral domain that does not reveal whose instance it is.
Most common mistakes
- Port 5678 published "just for a moment". Docker bypasses ufw, so n8n is reachable without HTTPS while the firewall looks closed.
- No cloud provider firewall. The one layer Docker cannot get around.
- An instance without an owner exposed to the internet. Whoever opens the address first takes it.
- The encryption key only on the server. A disk failure then means losing every credential, even with a current database backup.
latestas the image tag. Everydocker compose pull, for example in a script meant only to change a setting, then updates n8n and runs a database migration nobody planned.- Public webhooks without authentication, and tokens hard-coded in nodes.
n8n auditfinds them in seconds. NODES_EXCLUDE=[], which quietly turns Execute Command back on.N8N_METRICS=truewithout a block in the proxy, which makes the instance's metrics public.
Frequently asked questions
- Is n8n secure?
- n8n ships with sensible defaults: it encrypts stored credentials, blocks the Execute Command node from version 2.0 and runs code in a separate process. Whether a self-hosted instance is secure depends on its configuration: which ports are open, whether the editor is public, whether webhooks require authentication and whether the encryption key is backed up. n8n does not enforce webhook authentication; you have to do that yourself.
- What happens if I lose the n8n encryption key?
- Every stored credential becomes unreadable and has to be entered again. A database backup without the matching key does not help. That is why N8N_ENCRYPTION_KEY is set explicitly before the first start and stored off the server, for example in a password manager.
- Can n8n behind a VPN still receive webhooks?
- Yes. The editor and the API go behind the VPN, while the production webhook and form paths are published separately, ideally on their own subdomain or through a WAF gateway that only lets through requests with a valid token. OAuth callbacks are opened by the user's browser, so they work over the VPN as usual.
- Is Cloudflare Access enough instead of a VPN?
- For protecting the editor, yes. It is a zero-trust model: access goes to a signed-in person, not to anyone on the network. The webhook paths then have to be excluded from Access, because external systems cannot pass the login, and protected with authentication in the Webhook node. AI agents and n8n-mcp get through Access with a service token.
- Does the free version of n8n have 2FA?
- Yes, every Community Edition user can turn on two-factor authentication for their own account. Only enforcing 2FA for all users of an instance requires a paid plan.
- How often should I update self-hosted n8n?
- The n8n docs recommend at least once a month, so you never jump many versions at once. Before every update I read the release notes and back up the database, because database migrations only run forward. Critical security fixes go in faster, within days.
Data behind this guide
Figures from the n8n AI Automation Index, refreshed weekly.
- How do AI agents (Claude Code, Cursor) build n8n workflows through MCP: build or maintain?
Maintain. Of 1,684,118 tool calls last week, 56% read workflows, executions and lists, and 12% write. Fetching executions alone is 30% of calls. In the same week 73,123 workflows were created, 10,446 a day.
Related guides
Want someone to secure your n8n?
I set up self-hosted n8n with the controls from this checklist and review instances that are already running. The first stage of a rollout costs €1,000 to €2,000 and ends with an instance that has backups and one integration in production.
Process automation in n8n