By the end of this tutorial, you’ll have n8n running on your own server with HTTPS, basic auth, and a working Claude integration — fully isolated from cloud services and ready for sensitive workflows like contract processing, internal lead scoring, or customer data pipelines.
The n8n self-hosted setup is the right call whenever you’re processing data that can’t legally or practically leave your infrastructure: PII, financial records, legal documents, or anything under strict data residency requirements. The cloud version is convenient, but you’re routing your workflow data through n8n’s servers. On-premise eliminates that. Here’s how to do it properly.
- Install Docker and Docker Compose — set up the runtime environment on your server
- Configure n8n with environment variables — set encryption key, authentication, and database
- Set up NGINX reverse proxy with SSL — terminate TLS and expose n8n securely
- Add Claude API credentials — wire in Anthropic’s API for AI-powered nodes
- Build a test workflow with Claude — validate the integration end-to-end
- Harden the deployment — lock down ports, add monitoring, and schedule backups
Step 1: Install Docker and Docker Compose
This guide assumes Ubuntu 22.04 LTS on a VPS or bare metal machine. A 2-core, 4GB RAM instance is sufficient for most teams — I’ve run 50+ active workflows on a $24/month Hetzner CX31. For anything heavier, check the serverless platform comparison — there are cases where a managed compute layer makes more sense than a raw VPS.
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Install Docker Compose plugin
sudo apt-get install -y docker-compose-plugin
# Verify
docker compose version # Should return v2.x
Step 2: Configure n8n with Environment Variables
Create a dedicated directory and write your docker-compose.yml. The two things most tutorials skip: setting a real N8N_ENCRYPTION_KEY and using Postgres instead of the default SQLite. SQLite is fine for one person; it will bite you the moment you have concurrent workflow executions or need to restore from backup under pressure.
# /opt/n8n/docker-compose.yml
version: "3.8"
services:
postgres:
image: postgres:15-alpine
restart: always
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD} # from .env file
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n_internal
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "127.0.0.1:5678:5678" # bind to localhost only — NGINX handles external traffic
environment:
- N8N_HOST=${DOMAIN}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY} # 32+ char random string
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${AUTH_USER}
- N8N_BASIC_AUTH_PASSWORD=${AUTH_PASSWORD}
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
- DB_POSTGRESDB_DATABASE=n8n
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=336 # prune after 2 weeks
- N8N_LOG_LEVEL=info
- WEBHOOK_URL=https://${DOMAIN}/
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
networks:
- n8n_internal
volumes:
postgres_data:
n8n_data:
networks:
n8n_internal:
Create your .env file in the same directory:
# /opt/n8n/.env — chmod 600 this file immediately
DOMAIN=n8n.yourdomain.com
ENCRYPTION_KEY=$(openssl rand -hex 32)
AUTH_USER=admin
AUTH_PASSWORD=$(openssl rand -base64 20)
DB_PASSWORD=$(openssl rand -base64 20)
# Print generated values so you can save them
echo "ENCRYPTION_KEY, AUTH_PASSWORD, DB_PASSWORD — save these now"
Critical: back up your ENCRYPTION_KEY somewhere secure immediately. If you lose it and have stored credentials in n8n, those credentials are unrecoverable. This is not a theoretical risk — it’s the most common painful mistake in self-hosted n8n deployments.
Step 3: Set Up NGINX Reverse Proxy with SSL
Certbot handles the TLS certificate. The NGINX config below adds a few security headers that the n8n defaults don’t include.
sudo apt install -y nginx certbot python3-certbot-nginx
# Get certificate
sudo certbot --nginx -d n8n.yourdomain.com
# /etc/nginx/sites-available/n8n
server {
listen 443 ssl http2;
server_name n8n.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "no-referrer";
add_header Permissions-Policy "camera=(), microphone=()";
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # required for websockets
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;
proxy_read_timeout 300s; # long enough for Claude to respond
}
}
server {
listen 80;
server_name n8n.yourdomain.com;
return 301 https://$host$request_uri;
}
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# Start n8n
cd /opt/n8n && docker compose up -d
Step 4: Add Claude API Credentials
Once n8n is running, navigate to your domain and log in with your basic auth credentials. Go to Settings → Credentials → New Credential and search for “Anthropic”.
Paste your Anthropic API key. n8n encrypts this at rest using the ENCRYPTION_KEY you set — it never leaves your server except in outbound API calls to api.anthropic.com. If your compliance requirements prohibit even that, you’re looking at self-hosting an open-source model instead; see the self-hosting vs Claude API cost breakdown for a realistic assessment of that tradeoff.
For team deployments, consider creating a dedicated Anthropic API key per environment (dev/prod) so you can rotate them independently and attribute costs accurately. If cost tracking is a concern on multi-user instances, the LLM cost calculator guide covers how to build usage attribution into your workflow design.
Step 5: Build a Test Workflow with Claude
Create a new workflow. Use an HTTP Request node as a test trigger, then add an Anthropic node. Here’s what the HTTP Request node configuration in a Code node looks like when you’re calling Claude directly via the HTTP node (useful if the native Anthropic node version doesn’t match your needs):
// n8n Code node — call Claude API directly
const response = await $http.request({
method: 'POST',
url: 'https://api.anthropic.com/v1/messages',
headers: {
'x-api-key': '{{ $credentials.anthropicApi.apiKey }}',
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: {
model: 'claude-3-5-haiku-20241022', // ~$0.0008 per 1K input tokens
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Classify the following support ticket into one of: billing, technical, general.\n\nTicket: ${$input.first().json.ticket_text}`
}
]
},
json: true,
});
return [{ json: { classification: response.content[0].text } }];
This pattern — using the HTTP Request node with a Code node for pre/post processing — gives you more control than the native Anthropic node and makes it easier to swap models or adjust headers. For production classification workflows, also look at how email triage workflows with n8n and Claude are structured, since the routing logic transfers directly.
Step 6: Harden the Deployment
Firewall rules
sudo ufw allow 22/tcp # SSH — restrict to your IP in production
sudo ufw allow 80/tcp # HTTP (redirect only)
sudo ufw allow 443/tcp # HTTPS
sudo ufw deny 5678/tcp # block direct n8n access — NGINX only
sudo ufw enable
Automated backups
#!/bin/bash
# /opt/n8n/backup.sh — run via cron daily
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/opt/backups/n8n"
mkdir -p $BACKUP_DIR
# Dump Postgres
docker compose -f /opt/n8n/docker-compose.yml exec -T postgres \
pg_dump -U n8n n8n | gzip > "$BACKUP_DIR/n8n_db_$TIMESTAMP.sql.gz"
# Also back up the .n8n volume (contains workflow files, if any)
docker run --rm \
-v n8n_n8n_data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf "/backup/n8n_data_$TIMESTAMP.tar.gz" /data
# Delete backups older than 14 days
find $BACKUP_DIR -name "*.gz" -mtime +14 -delete
# Add to crontab
echo "0 2 * * * /opt/n8n/backup.sh >> /var/log/n8n_backup.log 2>&1" | crontab -
Enable execution logging
Add these to your .env to get structured logs you can pipe to a log aggregator:
N8N_LOG_OUTPUT=console
N8N_LOG_LEVEL=info
# For Loki/Grafana: mount the log output via Docker logging driver
For more advanced observability — tracking which Claude calls fail, latency per workflow, and token usage per execution — see the observability guide for production Claude agents. The same patterns apply inside n8n workflows.
Common Errors
1. Webhook URLs returning 404 after container restart
This happens when WEBHOOK_URL doesn’t match your actual domain, or when you changed N8N_HOST without updating it. n8n bakes the webhook URL at registration time. Fix: set WEBHOOK_URL=https://n8n.yourdomain.com/ explicitly in your env and restart. Then delete and re-create any affected webhooks — they won’t auto-update.
2. “Credentials could not be decrypted” after restoring from backup
You restored the database but used a different N8N_ENCRYPTION_KEY. The credentials in Postgres are AES-encrypted with that key. Always back up the .env file alongside the database dump. If you’ve lost the key, you have to re-enter all credentials manually — there is no recovery path.
3. Claude API calls timing out in long workflows
The default NGINX proxy_read_timeout is 60 seconds. Claude’s larger models can take 30–90 seconds on long prompts. Set proxy_read_timeout 300s in NGINX (included in the config above) and set N8N_DEFAULT_TIMEOUT=300000 in your n8n environment. Also check your VPS firewall — some providers kill idle connections at 60s regardless of NGINX settings.
What to Build Next
The most immediate extension: add a second n8n instance for staging with its own database, running on the same server behind a different subdomain (e.g., n8n-staging.yourdomain.com). Use Docker Compose profiles to manage both. This lets you test workflow changes against real Claude API responses before pushing to production — the same pattern used in social media automation workflows where a broken workflow hitting a live API is an actual problem, not just a theoretical one.
Once staging is set up, wire in a Git-based workflow backup using n8n’s CLI export: n8n export:workflow --all --output=/backups/workflows/ on a schedule, committed to a private repo. Now you have version control for your automations.
Frequently Asked Questions
How much does it cost to self-host n8n?
The n8n software is free under the Sustainable Use License for self-hosted deployments. Your main cost is the VPS: a Hetzner CX31 (3 vCPU, 8GB RAM) runs about €15/month and handles 100+ active workflows comfortably. Add your Anthropic API costs on top — Claude 3.5 Haiku is roughly $0.0008 per 1K input tokens, so a workflow processing 500 documents/day costs under $5/month at typical prompt lengths.
What’s the difference between n8n cloud and n8n self-hosted?
n8n cloud manages hosting, updates, and backups for you — starting at $20/month. Self-hosted gives you full data control, no execution limits based on pricing tier, and the ability to run on air-gapped infrastructure. The tradeoff is you own the ops work: updates, backups, monitoring, and any downtime. For workflows processing sensitive data, self-hosted is usually the right call regardless of team size.
Can I use n8n self-hosted with Claude without the native Anthropic node?
Yes — the HTTP Request node works perfectly. Send POST requests to https://api.anthropic.com/v1/messages with your API key in the header. This is actually preferable in some cases because you control the exact request shape, can pin the anthropic-version header, and the workflow is more explicit. The native node abstracts some of that, which can obscure what’s actually happening when something breaks.
How do I update n8n without losing my workflows?
Run docker compose pull && docker compose up -d from your n8n directory. Your workflows are stored in Postgres (not the container), so pulling a new image won’t affect them. That said, always take a database backup before major version upgrades — n8n runs migrations on startup that can’t be rolled back. Check the n8n changelog for breaking changes before jumping across major versions.
Is basic auth enough to secure a self-hosted n8n instance?
For a team environment, no. Basic auth over HTTPS is acceptable for a solo developer, but for teams you should either use n8n’s built-in user management (available since v0.214) or put it behind a VPN so it’s not publicly accessible at all. The webhook endpoints are the real attack surface — anyone who finds your webhook URL can trigger workflows. Use webhook auth tokens and consider allowlisting source IPs for sensitive webhooks.
Put this into practice
Try the Api Security Audit agent — ready to use, no setup required.
Editorial note: API pricing, model capabilities, and tool features change frequently — always verify current details on the vendor’s website before building in production. Code examples are tested at time of writing; pin your dependency versions to avoid breaking changes. Some links in this article may be affiliate links — we may earn a commission if you sign up, at no extra cost to you.

