Why this matters more than you think
n8n ends up holding the keys to your stack. It talks to billing, CRMs, source control, inboxes, and private APIs.
One sloppy setting on a VPS can leak a token or let anyone slam a public webhook. Good security is not a scary checklist. It is a series of small choices that remove surprises.
We will go layer by layer and dial in a configuration that stays boring and safe.
What we are protecting
Data that lives in or flows through n8n
- Credentials stored in the database. These are encrypted with a key in the
.n8n
directory. - Workflow definitions and execution history that may include sensitive payloads.
- Binary data if you use filesystem storage in single node mode.
- The editor which exposes admin functions to whoever reaches it.
Surfaces that attackers poke first
- Public webhooks with guessable URLs or missing signature checks.
- Reverse proxy that forgets to forward scheme and host or leaves HTTP open.
- Database reachable from the internet or running with default credentials.
- Out-of-date containers with known vulnerabilities.
Threat model in plain words
We are not defending against nation states. We are defending against scanners, opportunistic botnets, careless tokens in logs, and simple mistakes under pressure.
The right move is to shrink what is exposed, add strong identity at the edge, encrypt at rest, validate inbound requests, and watch for anomalies.
Secure-by-default VPS baseline
SSH and operating system
- Disable password login and move to key based auth only.
- Restrict SSH to specific IPs if your workflow allows it.
- Keep unattended security updates on.
- Create a non-root user with sudo then stop logging in as root.
Quick commands
# disable password auth
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl reload ssh
# basic ufw policy
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
Network segmentation
Keep PostgreSQL and Redis off the public internet. Use private networking between containers or VMs. Only 80 and 443 should face the world. SSH if you need it.
Docker hygiene without superstition
Image choices and upgrades
- Use official n8n images. Pin a specific version so you can roll back.
- Update regularly on a schedule. Keep the previous tag available for quick revert.
Least privilege containers
- Do not run with
--privileged
. - Drop capabilities you do not need.
services:
n8n:
image: n8nio/n8n:1.50.2
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
Secrets handling
- Keep secrets in
.env
outside version control. - Reference with
env_file
or environment variables in Compose. - Never paste tokens into code nodes. Use n8n credential types instead.
Reverse proxy and TLS that never get in your way
Why a reverse proxy is not optional
TLS termination, redirects, rate limiting, security headers, and backend isolation all live here. Exposing port 5678 to the internet will hurt you.
Environment variables n8n needs behind a proxy
Set these in .env
so internal links and public webhook URLs are correct.
N8N_HOST=automation.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/
N8N_PORT=5678
N8N_PROXY_HOPS=1
N8N_SECURE_COOKIE=true
Nginx example with sane defaults
server {
listen 80;
server_name automation.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name automation.example.com;
ssl_certificate /etc/letsencrypt/live/automation.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/automation.example.com/privkey.pem;
# security headers for the editor UI
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# bump timeouts for long running requests
client_max_body_size 25m;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# optional rate limiting per IP
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
location / {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Caddy example for a clean setup
automation.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:5678
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
}
}
Do not do path based routing unless you must
Putting n8n at example.com/n8n/
complicates everything. A subdomain is simpler for TLS, headers, and webhook URLs.
Identity and access to the editor
Basic auth for small teams or labs
n8n has built-in basic auth. Turn it on when you only have one or two users.
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=strongPass_please
Lock down by IP at the proxy
Even if you have user management, limiting the editor to office IPs or a VPN is a great safety net.
location / {
allow 203.0.113.0/24;
deny all;
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
Session security
Set secure cookies with N8N_SECURE_COOKIE=true
. Keep the editor on HTTPS only. Log out shared sessions when staff changes. Remove stale user accounts. Do not keep default admin names.
Secrets and credentials that stay secret
Understand the encryption key
n8n encrypts credentials using a key stored under /home/node/.n8n
. If you rotate or lose this key, decryption fails. Back it up with the database then keep it out of repos.
openssl rand -hex 32 # generate a strong key for N8N_ENCRYPTION_KEY
Practical rules that avoid leaks
- Reference secrets with credential types.
- Avoid printing secrets to logs.
- Keep
.n8n
on a dedicated volume that is part of your backup plan. - Limit who can read the volume on the host.
Database and storage decisions that affect security
PostgreSQL on private network
- Bind to localhost or a private interface.
- Use a strong password for the n8n user.
- Keep Postgres on NVMe for predictable I/O which helps reduce timeouts and log dumps.
- Back up daily and test restores monthly.
SQLite in production
SQLite can work for small dev setups but is fragile under concurrent writes. If you care about uptime and data integrity move to PostgreSQL before launch.
Execution history and data retention
Keep only what you need. Large execution logs increase risk and slow down incident response. Prune automatically.
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL=72
Webhook security that holds up on bad days
Start with long random URLs
n8n generates long paths for webhooks. Do not shorten them. Treat the full URL as a secret that still needs verification.
Validate signatures from providers
- GitHub: HMAC SHA256 with a shared secret in
X-Hub-Signature-256
. - Stripe: Signed payload with timestamp in
Stripe-Signature
. - Twilio: HMAC in
X-Twilio-Signature
using the request URL.
You can validate in a Code node if a native node is not used. Here is a GitHub example in a Code node right after a webhook trigger.
const crypto = require('crypto');
const secret = $env.GITHUB_WEBHOOK_SECRET; // add to env or credentials
const sig = $json.headers['x-hub-signature-256']; // from webhook headers
const payload = $json.body_raw; // raw body from trigger if available
const hmac = crypto.createHmac('sha256', secret).update(payload).digest('hex');
if (!sig || !sig.startsWith('sha256=')) {
throw new Error('Missing signature');
}
if (sig !== `sha256=${hmac}`) {
throw new Error('Signature mismatch');
}
return items;
If your flow uses native nodes for these providers, use their built-in verification instead of custom code.
Rate limit and allowlist when possible
- Add
limit_req
in Nginx to slow brute force attempts. - If the provider publishes IP ranges, allowlist them at the firewall for high risk endpoints.
- Consider mutual TLS for internal webhooks between your services and n8n.
Handle slow providers without cascading failure
Extend proxy timeouts to handle long upstream calls. Do not log raw payloads that include secrets. Consider dropping verbose logs in hot paths.
Supply chain and updates
Third party nodes
Community nodes are useful but risky if you install blindly. Review code or pick well known packages. Pin versions. Test updates on staging.
Keep n8n and dependencies current
- Track n8n release notes. Plan small regular upgrades.
- Update Redis and Postgres on their own cadence.
- Keep the reverse proxy current for TLS fixes and performance.
Observability that helps security
Metrics worth watching
- Login failures or unexpected 401s at the proxy.
- 4xx and 5xx rates on webhooks.
- Queue depth and worker restarts if you use queue mode.
- Postgres connections and slow query count.
Logs without oversharing
Centralize logs with Loki or Elasticsearch then mask secrets. Log IDs and error surfaces not entire bodies for payment or personal data.
Backups with security in mind
- Encrypt database dumps at rest.
- Store backups off the VPS.
- Practice a timed restore on a clean VM.
- Snapshot before upgrades then remove old snapshots on schedule.
Change management and small audits
- Keep proxy and Compose files in version control.
- Use code review for security related changes.
- After each upgrade run a short checklist: editor reachable, webhooks valid, queue moving, metrics green.
A concise hardening checklist you can copy
- VPS: SSH keys only and firewall on.
- Reverse proxy: HTTPS forced and security headers applied.
- n8n env: correct
N8N_HOST
,N8N_PROTOCOL
,WEBHOOK_URL
,N8N_SECURE_COOKIE
. - Database: Postgres private and backed up.
- Credentials: encryption key saved and not committed.
- Webhooks: signature validation plus rate limiting at the edge.
- Updates: pinned images and a rollback tag.
- Monitoring: alerts on 5xx spikes and queue backlog.
- Restore: tested monthly on a clean VM.
FAQ
How do I restrict access to the n8n editor without breaking webhooks?
Put the editor behind a reverse proxy that enforces IP allowlists or basic auth. Webhooks stay public on their long URLs. For teams use built-in user management and still keep a proxy rule that limits where the editor can be reached from.
Do I need HTTPS if my provider sits behind a CDN?
Yes. Terminate TLS at the CDN then forward scheme and host to your proxy. Set N8N_PROTOCOL=https
and a correct WEBHOOK_URL
so n8n generates secure links.
Should I expose PostgreSQL for convenience?
No. Keep it on localhost or a private network. If you must access it remotely use a VPN and TLS then restrict by IP.
Is Redis a security concern in queue mode?
Treat Redis like Postgres. Do not expose it publicly. Keep it on private networking and protect it with your provider firewall.
What is the one thing people forget most?
They forget the encryption key in .n8n
. Back it up with the database. Without it your credentials are gone after a restore.
Can I rely on provider retries instead of monitoring webhooks?
No. Some providers retry then give up. Monitor uptime and error rates from outside and watch execution failures inside so you can catch problems fast.