You followed our self-hosted n8n Docker guide, your workflows are running, and data is flowing between your services. Now the question is: how exposed is this thing?

The default n8n installation is designed for convenience, not security. That's fine for local development. It is not fine for a production instance connected to your Stripe account, your CRM, your email provider, and your production database. This checklist covers every layer — from the operating system up through n8n's own configuration — so you can harden your instance methodically rather than discovering gaps after an incident.

1. Why n8n Security Matters

n8n is a credential aggregator by design. Every workflow that connects to an external service stores authentication material: API keys, OAuth tokens, database connection strings, SMTP credentials, webhook secrets. A single n8n instance running 20 workflows might hold credentials for Stripe, HubSpot, PostgreSQL, SendGrid, Slack, Google Sheets, and a dozen other services. Compromise the n8n instance and you have the keys to all of them.

This is not theoretical. A publicly exposed n8n instance with default settings — no authentication, no encryption key, port 5678 open to the internet — is a treasure trove for anyone who finds it. Shodan and similar scanning tools index exposed n8n instances regularly. Even with basic authentication enabled, a weak password and no rate limiting means brute-force attacks are trivial.

The attack surface extends beyond the editor UI. n8n exposes webhook endpoints that accept inbound HTTP requests. If your workflows process webhook data without validation, an attacker can trigger workflow executions with crafted payloads. Execution logs may contain sensitive data from previous runs — API responses, customer records, financial transactions — and these persist in the database unless you configure retention limits.

The hardening steps below address each of these vectors. None of them are optional for a production deployment.

2. Server-Level Hardening

Before touching n8n's configuration, lock down the server itself. These steps apply to any Ubuntu/Debian VPS, whether you're running n8n or anything else.

Configure the firewall

UFW (Uncomplicated Firewall) ships with Ubuntu. Enable it with a default-deny policy and only allow the ports you need:

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH, HTTP, and HTTPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable the firewall
sudo ufw enable

# Verify rules
sudo ufw status verbose

That's it. Port 5678 (n8n's default) is intentionally not opened — n8n will sit behind a reverse proxy on ports 80/443. Never expose 5678 directly to the internet.

Install Fail2ban for SSH protection

Fail2ban monitors log files and bans IPs that show malicious patterns. For SSH brute-force protection:

sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

The default configuration bans an IP for 10 minutes after 5 failed SSH attempts. For a production server, create a stricter local override:

sudo cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
filter = sshd
maxretry = 3
bantime = 3600
findtime = 600
EOF

sudo systemctl restart fail2ban

This bans IPs for one hour after 3 failed attempts within a 10-minute window.

Disable root login and require SSH keys

# Generate SSH key on your local machine (if you haven't already)
ssh-keygen -t ed25519 -C "your_email@example.com"

# Copy public key to server
ssh-copy-id user@your-server-ip

# Then on the server, edit SSH config
sudo nano /etc/ssh/sshd_config

Set the following directives in /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3

Restart the SSH daemon:

sudo systemctl restart sshd

Enable automatic security updates

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades

This ensures critical security patches are applied automatically without manual intervention. Kernel updates still require a reboot, but application-level patches (OpenSSL, systemd, etc.) apply immediately.

3. n8n-Specific Security Configuration

With the server locked down, configure n8n itself. These settings go in your .env file or docker-compose.yml environment block.

Set the encryption key

This is the single most important security configuration in n8n. Generate a strong key:

openssl rand -hex 32

Add it to your environment:

N8N_ENCRYPTION_KEY=your_generated_64_character_hex_string
Warning

This key encrypts every credential in your database. Lose it = permanently lose access to all stored API keys, OAuth tokens, and database passwords. There is no recovery mechanism. Back up this key separately from your n8n database, store it in a password manager, and document it in your team's secrets vault.

If you deployed n8n without setting this key, n8n used a default key. That means your credentials are encrypted with a publicly known value — functionally equivalent to no encryption at all. Set a proper key, then re-enter all your credentials through the n8n UI so they're re-encrypted with the new key.

Disable public registration

After creating your first admin account, prevent anyone else from registering:

N8N_USER_MANAGEMENT_DISABLED=false
N8N_PUBLIC_API_DISABLED=false

In the n8n Settings UI, navigate to Users and ensure that sign-up is disabled. Only invite new users explicitly through the admin panel. For single-user instances, this prevents an attacker from creating their own account on an exposed instance.

Enforce HTTPS on webhooks

The WEBHOOK_URL must use HTTPS. This isn't just about encrypting data in transit — many external services (Stripe, GitHub, Slack) refuse to send webhooks to HTTP endpoints:

WEBHOOK_URL=https://n8n.yourdomain.com/
Tip

If WEBHOOK_URL is not set, n8n generates webhook URLs based on the hostname it detects. Behind a reverse proxy, this often results in http://localhost:5678 URLs that external services can't reach. Always set it explicitly.

Configure session timeout

By default, n8n sessions persist for a long time. For production instances — especially those accessible over the internet — reduce the session duration:

N8N_USER_MANAGEMENT_JWT_DURATION_HOURS=2

This forces re-authentication every 2 hours. Adjust based on your workflow — a team that's actively building automations might prefer 8 hours; a high-security environment should use 1-2 hours.

Prune execution data

Execution logs contain the input and output of every node in every workflow run. That includes API responses, customer data, and anything else your workflows process. Limit how long this data persists:

EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_PRUNE=true

This prunes execution data older than 168 hours (7 days). For workflows that process sensitive data — payment information, personal records, health data — consider a shorter retention window of 24-48 hours. You need enough history to debug failed workflows, but not enough to become a liability if the database is compromised.

Disable telemetry

n8n collects anonymized usage data by default. In regulated environments or when handling sensitive data, disable it:

N8N_DIAGNOSTICS_ENABLED=false

This prevents any data from being sent to n8n's telemetry servers. It has no impact on functionality.

4. Network Security

n8n's application-level settings protect the software. Network-level configuration protects the server from threats that never reach n8n's code.

Never expose port 5678 directly

n8n listens on port 5678 by default. This port should never be reachable from the public internet. All traffic should flow through a reverse proxy (Caddy, Nginx, or Traefik) that terminates TLS on ports 80/443 and forwards requests internally to 5678.

If you followed our self-hosting guide, Caddy handles this automatically. Verify that port 5678 is not exposed by checking your Docker Compose file — the n8n service should not have a ports directive mapping 5678 to the host. Only the reverse proxy needs published ports:

# Correct: only Caddy exposes ports
caddy:
  ports:
    - "80:80"
    - "443:443"

# n8n has NO ports section — only reachable via internal Docker network
n8n:
  expose:
    - "5678"

Test from an external machine: curl -v http://YOUR_SERVER_IP:5678 should time out or return connection refused. If it returns an n8n response, your firewall or Docker network configuration is leaking.

IP whitelisting for the editor

The n8n editor UI is the most sensitive attack surface — it provides full access to create, modify, and delete workflows, and to view stored credentials. Restrict editor access to known IP addresses using your reverse proxy.

In a Caddyfile, use the remote_ip matcher:

n8n.yourdomain.com {
    @blocked not remote_ip 203.0.113.10 198.51.100.0/24
    respond @blocked 403

    reverse_proxy n8n:5678
}

This allows access only from the specified IP address and CIDR range, returning 403 Forbidden to everyone else. Webhook endpoints still need to be publicly reachable, so a more targeted approach whitelists the editor paths while leaving webhook paths open:

n8n.yourdomain.com {
    # Webhook endpoints — open to the internet
    handle /webhook/* {
        reverse_proxy n8n:5678
    }
    handle /webhook-test/* {
        reverse_proxy n8n:5678
    }

    # Everything else (editor, API) — restricted by IP
    handle {
        @blocked not remote_ip 203.0.113.10
        respond @blocked 403
        reverse_proxy n8n:5678
    }
}

VPN for admin access

For teams or remote administrators, a VPN tunnel is more robust than IP whitelisting. Tools like WireGuard (lightweight, fast, built into the Linux kernel) or Tailscale (zero-config WireGuard overlay) let you place the n8n editor behind a private network. Admin traffic flows over the encrypted tunnel; webhook traffic flows over the public internet to the reverse proxy. This eliminates the need to update IP whitelists when team members change locations.

Rate limiting on webhook endpoints

Webhook URLs are public by design. Without rate limiting, an attacker who discovers a webhook URL can flood it with requests, consuming n8n execution resources and potentially triggering expensive downstream API calls.

Caddy's rate_limit directive (available via plugin) or an upstream Cloudflare proxy can enforce per-IP rate limits. A reasonable starting point: 60 requests per minute per IP on webhook paths. Legitimate webhook senders (Stripe, GitHub, Shopify) never approach this rate from a single IP.

5. Credential Management

n8n stores credentials for every connected service. How you manage these credentials determines your blast radius if something goes wrong.

Prefer OAuth over API keys

Where the service supports it, use OAuth2 authentication instead of static API keys. OAuth tokens are scoped (you can grant only the permissions n8n needs), time-limited (tokens expire and refresh automatically), and revocable (disable the OAuth app without rotating all keys). API keys are typically permanent, broadly scoped, and revocation means every integration using that key breaks simultaneously.

Apply least-privilege scopes

When creating API keys or OAuth connections, grant only the permissions each workflow requires. A workflow that reads data from HubSpot does not need write access. A workflow that sends Slack messages does not need channel management permissions. If a credential is compromised, least-privilege scoping limits what an attacker can do with it.

Rotate credentials on a schedule

Establish a rotation cadence. For high-sensitivity credentials (payment processors, production databases): rotate every 90 days. For medium-sensitivity (CRM, email providers): rotate every 180 days. After any security incident or team member departure: rotate immediately. n8n makes rotation straightforward — update the credential in the n8n UI and test the affected workflows. Document which workflows use which credentials so rotation does not become a guessing game.

Use environment variables for infrastructure secrets

Database passwords, the encryption key, and Redis credentials should live in a .env file referenced by Docker Compose, not hardcoded in docker-compose.yml:

# .env file (chmod 600, owned by root)
N8N_ENCRYPTION_KEY=your-generated-key-here
POSTGRES_PASSWORD=strong-random-password
REDIS_PASSWORD=another-strong-password

Set file permissions so only root can read the .env file: chmod 600 .env && chown root:root .env. This prevents other users on a shared system from reading your secrets.

6. Monitoring & Logging

Security without monitoring is a locked door with no alarm system. You need to know when something abnormal happens.

Monitor execution patterns

In the n8n UI, review the Executions tab regularly. Look for unusual patterns: workflow executions at unexpected hours, workflows triggered by unknown webhook sources, or a sudden spike in execution volume. These can indicate unauthorized access or webhook endpoint abuse.

Set up external health monitoring

An external monitoring tool like Uptime Kuma (self-hosted, minimal resource footprint) can watch your n8n instance from outside. Configure it to check:

Configure alerts via Slack, email, or Telegram so you are notified within minutes of any failure.

Centralize Docker logs

Docker container logs capture n8n application errors, authentication failures, and webhook processing details. By default, these logs stay on the local filesystem and are lost if the container is recreated. At minimum, configure Docker log rotation (as described in our backup guide) and review logs periodically:

# Check for authentication errors
docker compose logs n8n --since 24h | grep -i "auth\|login\|unauthorized"

# Check for webhook errors
docker compose logs n8n --since 24h | grep -i "webhook\|error"

For production environments, forward logs to a centralized system (Loki, Elasticsearch, or a managed logging service) so they survive container restarts and can be searched historically.

7. Infrastructure-Level Protection

Application hardening protects n8n from software-level threats. Infrastructure hardening protects the server and network from threats that operate below the application layer.

DDoS protection

Webhook endpoints are publicly reachable URLs. A targeted DDoS attack against your webhook URLs can overwhelm your server's network capacity, making the n8n editor inaccessible and causing all webhook deliveries to time out. MassiveGRID provides 12 Tbps DDoS mitigation across all VPS plans — traffic scrubbing happens at the network edge before malicious packets reach your server. Most budget VPS providers offer no DDoS protection or cap it at volumes too low to matter.

Network isolation

Your n8n containers (main process, workers, PostgreSQL, Redis) should communicate over an isolated Docker network that is not bridged to the host's public interface. The default Docker Compose configuration creates this internal network automatically. Verify that your PostgreSQL and Redis containers have no published ports — they should only be reachable by other containers on the same Docker network.

Storage encryption

n8n's N8N_ENCRYPTION_KEY encrypts credentials within the database. But the database files themselves sit on disk. If an attacker gains filesystem access (through a compromised application or stolen backup), unencrypted database files expose execution history, workflow definitions, and metadata. MassiveGRID's Ceph distributed storage encrypts data at rest across all storage nodes, providing an additional layer of protection independent of application-level encryption.

Physical security

This one is easy to overlook because it is out of sight. MassiveGRID's data centers are operated by Equinix and Digital Realty — facilities with biometric access controls, 24/7 security staffing, and CCTV monitoring. This matters for regulated industries (healthcare, finance, government) where physical access controls are part of compliance audits. If your compliance framework requires physical security documentation, ask your VPS provider for their SOC 2 or ISO 27001 certification status.

Perspective

For personal projects or low-sensitivity automations, server-level hardening (sections 2–3) is sufficient. Infrastructure-level protection becomes genuinely important when your n8n instance handles financial data, personal records, or healthcare information — cases where a breach has legal and financial consequences beyond the immediate technical damage.

Quick Reference Checklist

Copy this checklist and work through it for every production n8n deployment:

Layer Item Command / Setting
Server UFW firewall enabled (22, 80, 443 only) sudo ufw status
Server Fail2ban active for SSH sudo fail2ban-client status sshd
Server Root login disabled, SSH keys required /etc/ssh/sshd_config
Server Automatic security updates enabled unattended-upgrades
n8n Custom encryption key set N8N_ENCRYPTION_KEY
n8n Public registration disabled Settings → Users
n8n HTTPS webhook URL configured WEBHOOK_URL=https://...
n8n Session timeout configured N8N_USER_MANAGEMENT_JWT_DURATION_HOURS
n8n Execution data pruning enabled EXECUTIONS_DATA_PRUNE=true
n8n Telemetry disabled N8N_DIAGNOSTICS_ENABLED=false
Network Port 5678 not exposed publicly curl YOUR_IP:5678 should fail
Network Editor access restricted (IP or VPN) Caddyfile / Nginx config
Credentials OAuth used where available n8n Credentials UI
Credentials Least-privilege scopes applied Per-service review
Credentials .env file permissions set (600) ls -la .env
Monitoring External health check configured Uptime Kuma or equivalent

None of these steps require significant time or expertise. A fresh n8n deployment can be hardened in under an hour by working through this list top to bottom. The most common mistake is not doing it at all — running a production instance with default settings because the defaults worked during testing.

Next Steps