n8n zelf hosten op VPS met Docker en HTTPS
Leer n8n installeren met Docker Compose en Postgres. Voorkom fouten bij de WEBHOOK_URL instelling en de encryption-key om uw workflows stabiel te houden.
Wat u bouwt
n8n is een tool voor workflow-automatisering: een visuele editor waarbij een trigger — zoals een webhook, een schema of een formulierinzending — een reeks nodes activeert. Deze nodes roepen API's aan, herstructureren data en schrijven naar andere systemen. Het is de standaardoplossing voor AI-agent workflows geworden, omdat het communiceert met elke modelprovider en database zonder dat u zelf een service hoeft te schrijven. Met één docker run heeft u binnen twee minuten een werkende editor. Deze handleiding gaat over de overige negentig procent: het systeem robuust maken door Postgres te gebruiken in plaats van het standaard SQLite-bestand, het bereikbaar maken via HTTPS, en — het onderdeel waar bijna iedereen een fout maakt — ervoor zorgen dat webhooks een URL vrijgeven die vanaf het internet bereikbaar is.
De voltooide stack bestaat uit twee containers op één Docker-netwerk: n8n zelf, en een Postgres-database waarin de workflows en credentials worden opgeslagen. Een reverse proxy op de host regelt de TLS-terminatie en stuurt het verkeer door naar n8n op localhost. Hierdoor is niets direct via het internet bereikbaar, behalve via deze proxy. Het maakt deel uit van de andere services op de 2026 self-hosting shortlist.
Vereisten en de beperkingen
U heeft een VPS nodig met minimaal 1 GB RAM. Reken op 2 GB zodra workflows intensief gebruik maken van resources. De executies en de Node.js runtime verbruiken veel geheugen. Het is zeer ongewenst als de out-of-memory killer de container tijdens de uitvoering afsluit. Een enkele vCPU is voldoende om te beginnen.
U heeft een domein of subdomain nodig — bijvoorbeeld n8n.example.com — met een A-record dat naar het publieke IP van de VPS wijst. Dit moet correct werken voordat u een certificaat aanvraagt. Poorten 80 en 443 moeten openstaan voor de proxy; de poort 5678 van n8n mag niet direct toegankelijk zijn via het internet. U heeft Docker Engine en de Compose-plugin nodig. Als docker compose version een foutmelding geeft met docker: 'compose' is not a docker command, dan gebruikt u de oude standalone binary en is de plugin sudo apt install docker-compose-plugin.
SQLite is geschikt voor tests, Postgres voor alles waar u op vertrouwt
De standaard database van n8n is een SQLite-bestand op /home/node/.n8n/database.sqlite. Voor tests is dit voldoende — als u geen volume mount, gaat de data verloren bij de eerste container recreate, wat een belangrijke les is. De reden om over te stappen naar Postgres is niet de ruwe snelheid; SQLite gebruikt een single writer lock. Een instantie die meerdere workflows tegelijk uitvoert, of de queue mode die u uiteindelijk wilt gebruiken, veroorzaakt SQLITE_BUSY: database is locked bij gelijktijdige acties (concurrency). Postgres heeft deze beperking niet, maakt probleemloos back-ups met pg_dump, en is de standaard die n8n zelf hanteert voor servers in productie. Later overstappen betekent handmatig data migreren. Gebruik daarom Postgres als de data belangrijk is.
DNS en de firewall
Wijs eerst het record toe en open de poorten. Dit voorkomt dat de certificaatstap later faalt omdat een naam niet wordt opgelost.
dig +short n8n.example.com
curl -s ifconfig.me
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow OpenSSH
sudo ufw enableOpen poort 5678 niet. Het compose file bindt n8n aan 127.0.0.1:5678, waardoor alleen de reverse proxy van de host erbij kan. Een ufw allow 5678 zou deze isolatie opheffen.
Het Compose-bestand
Maak een werkmap en een docker-compose.yml aan. Dit is de volledige stack — twee services, één privaat netwerk en twee benoemde volumes.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:2.29.10
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_HOST=n8n.example.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.example.com/
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=Europe/London
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
networks:
- n8n_net
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
n8n_data:
networks:
n8n_net:Enkele belangrijke keuzes. DB_POSTGRESDB_HOST=postgres is de service name, die Docker via het gedeelde netwerk oplost — niet localhost, wat binnen de n8n-container naar n8n zelf verwijst. De depends_on met condition: service_healthy voorkomt dat n8n en Postgres bij het opstarten conflicteren; zonder deze instelling start n8n op, vindt de database niet en stopt de container. Het benoemde volume n8n_data op /home/node/.n8n bevat de encryptiesleutel en bij SQLite de database — de enige map die u niet mag verliezen. Gebruik een exacte versie voor de image en gebruik nooit latest; de redenen hiervoor staan in de sectie over upgrades hieronder.
Het secrets file
Plaats nooit wachtwoorden in het compose file. Gebruik een .env file naast het compose file dat Compose automatisch inleest. Genereer deze wachtwoorden zodat ze echt willekeurig zijn.
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
chmod 600 .envDe N8N_ENCRYPTION_KEY is de belangrijkste string in dit proces — dit is de sleutel waarmee elk opgeslagen credential wordt versleuteld. Stel deze expliciet in in plaats van n8n een sleutel te laten genereren. Een zelf gegenereerde waarde kunt u namelijk opslaan en herstellen. Zodra n8n het eerste credential met deze sleutel heeft versleuteld, maakt het wijzigen ervan elk credential onleesbaar — stel deze dus eenmalig in en wijzig deze regel nooit meer.
De env vars die bepalen of webhooks werken
Vier variabelen bepalen hoe n8n zichzelf aan de buitenwereld presenteert. Fouten in deze variabelen zijn de meest voorkomende supportvraag bij n8n.
N8N_HOSTis de publieke hostname,n8n.example.com. Laat dit op de standaardwaardelocalhoststaan bij gebruik van een proxy. De editor probeert dan de eigen API te laden vialocalhostin uw browser, wat mislukt.N8N_PROTOCOL=httpsgeeft aan dat n8n via TLS wordt bediend. Hierdoor wordt de session cookieSecuregemarkeerd en wordenhttps://URL's gegenereerd.N8N_PORT=5678is de poort waarop n8n luistert binnen de container. Dit is niet de publieke poort; de proxy gebruikt poort 443.WEBHOOK_URL=https://n8n.example.com/is de meest kritieke variabele. n8n genereert de webhook-adressen voor Stripe, GitHub of andere externe systemen door deze waarden te combineren. Als deze variabele niet is ingesteld of onjuist is, valt n8n terug opN8N_HOST:N8N_PORT. Dit resulteert inhttps://n8n.example.com:5678/webhook/...of, erger nog,http://localhost:5678/webhook/.... Deze adressen lijken correct maar zijn onbereikbaar vanaf het internet, waardoor verzoeken van de externe partij zonder foutmelding niet aankomen. Stel deze in op de exacte publieke base URL met een trailing slash. Controleer daarna of de webhook node een URL zonder poort weergeeft.
N8N_PROXY_HOPS=1 zorgt ervoor dat de Express-server van n8n één proxy vertrouwt. Hierdoor zien rate-limiting en functies die het client IP uitlezen het echte adres in plaats van het adres van de proxy. Eén variabele die u hier bewust niet instelt is N8N_RUNNERS_ENABLED. Task runners — waarbij n8n de logica van de Code-node in een apart sandboxed proces uitvoert — zijn sinds versie 1.69 de standaard. Vanaf de 2.x lijn uit deze handleiding zijn ze verplicht, waardoor de oude opt-in is verouderd. Als u deze nu instelt, logt n8n enkel een melding dat u deze moet verwijderen.
Eerste start
docker compose up -d
docker compose ps
docker compose logs -f n8nEen succesvolle eerste boot eindigt met een Editor is now accessible via: regel, met een n8n ready on ..., port 5678 regel daarboven. docker compose ps moet beide containers Up tonen, waarbij postgres als (healthy) is gemarkeerd. Als n8n in een Restarting loop terechtkomt, controleer dan de logs — dit komt bijna altijd door de databaseverbinding of de volume-rechten die hieronder worden beschreven.
TLS met een reverse proxy
n8n gebruikt standaard HTTP op poort 5678; een component ervoor regelt de HTTPS-verbinding. Er zijn twee eenvoudige opties.
Als u al meerdere containers gebruikt, plaats n8n dan achter een Traefik reverse proxy die automatisch TLS-certificaten uitgeeft met enkele labels — Traefik vraagt het certificaat aan en vernieuwt dit voor u.
Als dit de enige applicatie op de server is, dan is een nginx virtual host met een Let's Encrypt-certificaat eenvoudiger. Gebruik de Certbot en nginx TLS setup voor Ubuntu 24.04 om het certificaat te verkrijgen, en gebruik vervolgens deze server block:
server {
listen 443 ssl;
server_name n8n.example.com;
ssl_certificate /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;
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";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600;
client_max_body_size 16m;
}
}De headers Upgrade en Connection "upgrade" zijn verplicht. n8n stuurt live updates van de uitvoering naar de editor via een WebSocket. Zonder deze twee regels wordt de inlogpagina geladen, maar blijft deze hangen met een banner die een verloren verbinding aangeeft. proxy_read_timeout 3600 voorkomt dat langlopende uitvoeringen worden afgebroken na de standaard 60 seconden van nginx. De X-Forwarded-Proto $scheme header is de aanvulling op N8N_PROXY_HOPS=1: deze informeert n8n dat de oorspronkelijke aanvraag via HTTPS verliep, ook al bereikt de proxy de applicatie via HTTP. Hierdoor markeert n8n de verbinding niet als onveilig en wordt het eigen cookie niet geweigerd.
Uw eerste workflow, om deze te realiseren
Open https://n8n.example.com/, maak het eigenaarsaccount aan (volgende sectie) en bouw de kleinste workflow om te bewijzen dat het pad werkt: een binnenkomende webhook, een HTTP-aanroep en een uitvoerige respons.
- Voeg een Webhook node toe. Stel de methode in op
POSTen gebruik een pad zoalshello. Er worden twee URL's getoond: een Test URL en een Production URL. Dit is de oorzaak van de helft van de meldingen over "mijn webhook werkt niet". De Test URL reageert op slechts één aanroep en alleen terwijl u op Listen for test event heeft geklikt; daarna verloopt deze. De Production URL reageert altijd wanneer de workflow Active is. - Voeg daarna een HTTP Request node toe, gericht op een publieke JSON API — een GET naar
https://api.github.com/zengeeft een string van één regel terug, wat voldoende is. - Voeg een Respond to Webhook node toe, en stel de Respond optie van de Webhook node in op "Using Respond to Webhook node". Hierdoor ontvangt de aanroeper de output van de HTTP node als respons.
- Zet de workflow op Active (rechtsboven) en roep deze aan:
curl -X POST https://n8n.example.com/webhook/hello. U zou de zen-regel terug moeten krijgen — POST binnen, API-aanroep, respons uit; dit is de basis van de meeste echte automatiseringen.
Een geplande variant vervangt de Webhook node door een Schedule Trigger en roept in plaats daarvan een model endpoint aan — een zelfgehoste variant via Ollama die draait op dezelfde VPS is een efficiënte manier om een nachtelijke summariser te bouwen.
Gebruikersbeheer, geen basic auth
Oudere n8n-handleidingen adviseren om N8N_BASIC_AUTH_ACTIVE=true in te stellen. Deze variabelen zijn verwijderd in n8n 1.0 en hebben geen effect meer. De authenticatie gebeurt tegenwoordig via het owner account: de eerste keer dat u de editor laadt, verplicht n8n u om een owner aan te maken met een e-mailadres en wachtwoord. Deze toegang is verplicht; er is geen anonieme modus. Maak dit account direct na de eerste start aan, voordat u de URL deelt: tussen docker compose up en de eerste formulierverzending kan de instantie worden toegeëigend door iedereen die de pagina bereikt. Een extra basic-auth laag via een reverse-proxy is een verstandelijke extra beveiliging, maar dit is een tweede factor en niet de primaire authenticatie.
Backups: eerst de encryptiesleutel, dan de database
Er moeten twee zaken worden gebackupt, en deze zijn niet gelijkwaardig vervangbaar.
De N8N_ENCRYPTION_KEY. Alle credentials die u in n8n opslaat — API tokens, databasewachtwoorden, OAuth secrets — zijn opgeslagen met encryptie via deze sleutel. De workflows in Postgres zijn onbruikbaar zonder deze sleutel: als u de database herstelt op een nieuwe machine met een andere sleutel, kan n8n geen enkele credential ontsleutelen. Er is geen mogelijkheid tot herstel of reset. Uw .env bestand bevat de sleutel; kopieer dit bestand naar een locatie buiten de server — een wachtwoordbeheerder is hiervoor ideaal — direct nadat u deze heeft aangemaakt. Dit is de backup die er echt toe doet.
De Postgres database, voor de workflows, de uitvoeringgeschiedenis en de versleutelde credentials zelf:
docker compose exec -T postgres pg_dump -U n8n -d n8n \
| gzip > n8n-db-$(date +%F).sql.gzVoer dit commando periodiek uit en kopieer de dump naar een externe locatie. Om te herstellen op een nieuwe VPS: start de stack één keer zodat de database bestaat, stop n8n, laad de dump terug met psql, plaats de dezelfde N8N_ENCRYPTION_KEY in .env, en start n8n. De combinatie van de juiste sleutel en de dump resulteert in een werkende instantie; een nieuwe sleutel resulteert in workflows die geen enkele credential kunnen gebruiken.
Upgrades: pin de tag
Het compose-bestand gebruikt met opzet n8nio/n8n:2.29.10 in plaats van latest. n8n brengt bijna wekelijks een nieuwe minor-versie uit. Tussen deze versies door verandert n8n soms het databaseschema of het gedrag van nodes. Dit betekent dat latest kan leiden tot een automatische update die direct de database migreert bij het opstarten. Pin een specifieke versie en lees de release notes voordat u de versie verhoogt. n8n vermeldt daar eventuele breaking changes. Voer upgrades bewust uit:
docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > pre-upgrade.sql.gz
# edit the image tag in docker-compose.yml, then:
docker compose pull n8n
docker compose up -d n8n
docker compose logs -f n8nDit is vooral belangrijk bij sprongen tussen major-versies. De 2.0-lijn veranderde bijvoorbeeld standaard N8N_BLOCK_ENV_ACCESS_IN_NODE naar true. Elke Code node die process.env gebruikte, verliest hierdoor ongemerkt de toegang, tenzij u dit terugzet naar false. Dezelfde release introduceerde strikte permissies voor het settings-bestand. Lees de 2.0 breaking-changes pagina voordat u een major-versie overgaat. n8n voert alle benodigde database-migraties automatisch uit bij het opstarten. Daarom is de pre-upgrade pg_dump niet optioneel. Omdat credentials versleuteld worden opgeslagen met een key in .env en de data in Postgres staat, zijn de containers vervangbaar. U voert een upgrade uit door de containers te vervangen. U voert een rollback uit door de vorige tag te gebruiken en de dump te herstellen.
Foutmodi, met de strings die u zult zien
The requested webhook "POST hello" is not registered. Een 404-fout bij het aanroepen van een webhook waarvan de workflow niet op Active staat, of bij het aanroepen van het testpad terwijl er niemand luistert. Testpaden (/webhook-test/...) reageren alleen als u op "Listen for test event" heeft geklikt; productiepaden (/webhook/...) reageren alleen wanneer de workflow-schakelaar aan staat. De verwante This webhook is not registered for GET requests. Did you mean to make a POST request? betekent dat de methode onjuist is — de node verwacht POST en u heeft GET verzonden.
De webhook URL toont een :5678 of localhost. De node geeft https://n8n.example.com:5678/webhook/... of http://localhost:5678/... weer. WEBHOOK_URL is niet ingesteld of onjuist, waardoor n8n het adres heeft opgebouwd vanuit N8N_HOST:N8N_PORT in plaats van uw publieke base. Stel WEBHOOK_URL=https://n8n.example.com/ in, maak de container opnieuw aan met docker compose up -d, en de poort verdwijnt.
There was a problem loading init data in de browser. De editor is geladen maar kan de eigen backend API niet bereiken. Achter een proxy is dit bijna altijd een onjuiste N8N_HOST of WEBHOOK_URL, een proxy die de WebSocket Upgrade headers mist, of een N8N_PROTOCOL die niet overeenkomt met de manier waarop u verbinding maakt. Controleer de vier publieke variabelen en of de proxy Upgrade en Connection doorstuurt.
password authentication failed for user "n8n" in de logs, waarbij de container herstart. Het wachtwoord dat n8n verzendt komt niet overeen met het wachtwoord waarmee de database is geïnitialiseerd. De valstrik: Postgres leest POSTGRES_PASSWORD alleen wanneer het een lege datadirectory initialiseert. Start de stack één keer, wijzig vervolgens POSTGRES_PASSWORD in .env, en het bestaande postgres_data volume bevat nog steeds het oude wachtwoord. Zet het terug naar het origineel, of, als u geen gegevens wilt behouden, gebruik dan docker compose down en docker volume rm op het postgres volume en start het opnieuw op.
EACCES: permission denied, open '/home/node/.n8n/config' bij het opstarten. n8n draait als de node gebruiker (UID 1000) en kan de configuratiedirectory niet beschrijven. Dit treedt op wanneer een hostmap (./n8n_data:/home/node/.n8n) die eigendom is van root wordt gebruikt via een bind-mount. Gebruik het hierboven getoonde named volume, of als u vasthoudt aan een bind-mount, gebruik dan eerst sudo chown -R 1000:1000 ./n8n_data.
Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. Vanaf versie 2.x dwingt n8n standaard 0600 af op dat instellingenbestand en herstelt dit zelf bij het opstarten — deze logregel betekent dat de modus al is gecorrigeerd, meestal na een bind-mount of nadat een restore het bestand met te ruime permissies heeft teruggekopieerd. Er is geen actie vereist; stel N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false alleen in als uw bestandssysteem de permissies daadwerkelijk niet kan ondersteunen.
Mismatching encryption keys — de volledige regel geeft aan dat de encryptiesleutel in het instellingenbestand /home/node/.n8n/config niet overeenkomt met de N8N_ENCRYPTION_KEY in uw omgeving. De sleutel in uw omgeving verschilt van de sleutel die n8n bij een eerdere sessie in het datavolume heeft geschreven — meestal omdat n8n een willekeurige sleutel heeft gegenereerd bij een eerdere start toen de variabele niet was ingesteld, en u daarna een andere heeft ingesteld. Plaats de originele sleutel terug in .env, of, alleen als u echt geen bewaarde gegevens heeft die het waard zijn, verwijder het config bestand in het n8n_data volume en laat n8n dit opnieuw genereren — houd er rekening mee dat bestaande gegevens dan onleesbaar worden.
Een inlogbanner over beveiligde cookies: Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL, or using Safari. U heeft N8N_PROTOCOL=https ingesteld maar heeft n8n bereikt via gewone HTTP — meestal door direct het IP en de poort te gebruiken in plaats van de HTTPS proxy. Bereik het via https://n8n.example.com/. Stel alleen N8N_SECURE_COOKIE=false in als u echt geen HTTPS kunt gebruiken, en nooit op een machine die met het internet verbonden is.
Om een taalmodel in deze workflows te gebruiken, zie building AI workflows with Claude and n8n.
FAQ
Moet ik SQLite of Postgres gebruiken voor n8n?
SQLite (de standaard) is geschikt om n8n te testen of voor een persoonlijke instantie die één workflow tegelijk uitvoert. Gebruik Postgres voor kritieke systemen: de single writer lock van SQLite veroorzaakt database is locked bij gelijktijdige processen. Postgres ondersteunt bovendien betrouwbare back-ups met pg_dump. Migratie achteraf is een handmatig proces; gebruik Postgres als de server belangrijk is.
Waarom worden mijn n8n webhooks nooit geactiveerd?
Meestal is de oorzaak WEBHOOK_URL. Als deze niet is ingesteld of onjuist is, genereert n8n webhook-adressen op basis van N8N_HOST:N8N_PORT — vaak met een :5678 of localhost erin — die geldig lijken maar onbereikbaar zijn vanaf het internet. Hierdoor komen de verzoeken van de aanroeper nooit aan. Stel WEBHOOK_URL=https://n8n.example.com/ in en controleer of de node een URL zonder poort weergeeft. De tweede oorzaak is het aanroepen van een webhook waarvan de workflow niet op Active staat, wat The requested webhook ... is not registered. oplevert.
Wat moet ik back-en in n8n?
Twee zaken. De N8N_ENCRYPTION_KEY uit uw .env bestand, omdat elke opgeslagen credential hiermee is versleuteld. Als u dit verliest, zijn de credentials permanent onleesbaar; kopieer dit bestand direct na aanmaak naar een andere locatie. Daarnaast een pg_dump van de Postgres-database voor de workflows, historie en credentials. Een herstel vereist beide: de juiste sleutel plus de dump.
Hoe plaats ik n8n achter HTTPS?
n8n gebruikt HTTP op poort 5678; een reverse proxy voor de server termineert de TLS-verbinding. Koppel n8n aan 127.0.0.1:5678 zodat alleen de proxy erbij kan. Gebruik vervolgens Traefik met automatische certificaten of nginx met een Let's Encrypt-certificaat. Stel N8N_PROTOCOL=https en WEBHOOK_URL=https://your-host/ in, en zorg dat de proxy de WebSocket Upgrade headers doorstuurt om te voorkomen dat de editor vastloopt.
Hoe upgrade ik n8n veilig?
Gebruik een specifieke image tag in plaats van latest. Maak eerst een pg_dump omdat n8n bij het opstarten automatisch migraties uitvoert. Lees de release notes voor breaking changes, verhoog de tag en voer docker compose pull n8n && docker compose up -d n8n uit. De container is vervangbaar; u kunt terugdraaien door de vorige tag te gebruiken en de dump van vóór de upgrade te herstellen.