Stack n8n pentru automatizari cu Docker Compose, PostgreSQL si HTTPS pe un VPS
Self-hosting

n8n cu Docker Compose: automatizari self-hosted pe VPS

Orice firma mica are cateva sarcini pe care cineva le face de mana, zi de zi: copiaza mesajele din formularul de contact intr-un tabel, trimite facturile din email catre contabilitate, anunta pe chat-ul echipei cand intra o comanda noua. Servicii precum Zapier sau Make rezolva frumos problema, pana cand factura lunara incepe sa creasca odata cu fiecare “task” rulat. n8n iti ofera acelasi tip de editor vizual pentru fluxuri de lucru, ruleaza pe serverul tau si nu te taxeaza pe executie. In ghidul de azi instalam n8n cu Docker Compose pe un VPS proaspat, cu PostgreSQL ca baza de date, Caddy in fata pentru HTTPS automat (ca webhook-urile din servicii externe sa mearga cu adevarat), o cheie de criptare corecta si fusul orar potrivit. La final construim o prima automatizare pe care o testezi cu o singura comanda.

Ce este n8n si de ce sa-l gazduiesti singur

n8n (se pronunta “n-eight-n”, vine de la “nodemation”) este o unealta de automatizare a fluxurilor de lucru. Un workflow este un lant de pasi care ruleaza automat. Fiecare pas este un nod: un nod primeste date, urmatorul le transforma, altul le trimite mai departe. Primul nod este mereu un trigger, adica evenimentul care porneste fluxul: un webhook (o cerere HTTP trimisa de alt serviciu), un program orar (“in fiecare zi la 08:00”), un email nou, un rand nou intr-un tabel.

Proiectul se afla pe n8n.io , iar codul sursa pe github.com/n8n-io/n8n . Vine cu sute de integrari (Google Sheets, Slack, Telegram, Stripe, GitHub, HTTP generic, SMTP, baze de date) si, spre deosebire de majoritatea uneltelor no-code, te lasa sa scrii JavaScript sau Python intr-un nod Code atunci cand blocurile vizuale nu iti ajung.

O nota despre licenta, pentru ca e importanta: n8n este “fair-code”, distribuit sub Sustainable Use License, nu sub o licenta open source clasica aprobata OSI. Practic, il poti gazdui gratuit pentru afacerea ta sau pentru uz personal; ce nu ai voie este sa revinzi n8n ca serviciu gazduit pentru altii. Cateva functii enterprise (SSO, permisiuni avansate) sunt platite.

De ce self-hosted si nu un abonament in cloud:

  • Nu platesti pe executie. Pe VPS-ul tau, un workflow care ruleaza de 50.000 de ori pe luna costa la fel ca unul care ruleaza de 50 de ori.
  • Datele raman la tine. Datele clientilor procesate de fluxuri nu parasesc serverul, ceea ce ajuta la GDPR cand serverul sta intr-un data center din Romania.
  • Acces la sisteme interne. n8n poate vorbi direct cu o baza de date sau un API din reteaua ta privata, fara tuneluri.

Compromisul e cinstit: devii responsabil de actualizari, backup si securitate. Mare parte din ghid e despre cum faci corect exact aceste lucruri.

De ce PostgreSQL si HTTPS, nu setarile implicite

Din cutie, n8n tine totul (workflow-uri, credentiale, istoric de executii) intr-un fisier SQLite. Pentru un test rapid e suficient, dar PostgreSQL gestioneaza mai bine executiile simultane, iti da backup-uri curate cu pg_dump si este obligatoriu daca mai tarziu vrei sa scalezi n8n in queue mode, cu procese worker. Daca pornesti direct pe PostgreSQL, scapi de o migrare mai tarziu.

Nici HTTPS nu e optional. Majoritatea serviciilor care trimit webhook-uri (Stripe, GitHub, boti Telegram, multe generatoare de formulare) refuza URL-uri HTTP simple, autentificarea OAuth pentru Google sau Microsoft cere o adresa de callback HTTPS, iar n8n seteaza cookie-uri securizate pe care browserele le accepta doar prin HTTPS. Fara domeniu si certificat, jumatate din integrarile interesante pur si simplu nu se vor conecta.

Cum se leaga piesele intre ele

Cateva definitii mai intai. Un container este un pachet izolat care contine o aplicatie si tot ce ii trebuie, ruland pe kernel-ul serverului gazda. Docker ruleaza containere, iar Docker Compose descrie mai multe containere intr-un singur fisier YAML, ca sa le pornesti, opresti si actualizezi impreuna. Un port este o “usa” numerotata a serverului: HTTP foloseste 80, HTTPS 443, n8n asculta pe 5678 si PostgreSQL pe 5432. Un reverse proxy este un server web care primeste traficul public si il trimite mai departe catre o aplicatie din spatele lui; folosim Caddy pentru ca obtine si reinnoieste automat certificatele Let’s Encrypt. Un volum este spatiu de stocare gestionat de Docker, care supravietuieste repornirilor si actualizarilor de container.

Stack-ul nostru are trei containere: Caddy (singurul care publica porturi spre internet), n8n si PostgreSQL. n8n si PostgreSQL sunt accesibile doar in reteaua interna Docker.

VPS DreamServer (Debian 13 / Ubuntu 24.04)Retea Docker (doar interna)CaddyTLS pe :80 / :443n8n:5678, nepublicatPostgreSQL 17:5432, nepublicatHTTPSQLcaddy_datan8n_datapostgres_dataInternetuseri, webhook-uriHTTPSServicii pe gazda (in afara Docker)ufwpermite 22/tcpsshdacces adminPorturile publicatede Docker ocolesc ufw(80/443 spre Caddy)Adminclient SSHSSH
Figura 1: traficul din internet ajunge la Caddy pe 80/443 (Docker publica direct aceste porturi, ocolind ufw). n8n si PostgreSQL sunt accesibile doar in reteaua Docker; ufw protejeaza serviciile de pe gazda, cum ar fi SSH.

De ce ai nevoie

  • Un VPS cu Debian 13 sau Ubuntu 24.04, minim 1 vCPU, recomandat 2 GB RAM (1 GB merge pentru uz usor, cu swap) si in jur de 20 GB de disc.
  • Un utilizator cu drepturi sudo si acces SSH.
  • Un domeniu sau subdomeniu la care poti edita DNS-ul, de exemplu n8n.example.com. Folosim acest nume in toate exemplele: inlocuieste-l cu al tau.
  • Porturile 80 si 443 accesibile din internet (Let’s Encrypt valideaza prin portul 80).
  • Un VPS KVM este varianta cea mai simpla. Docker intr-un container LXC neprivilegiat are nevoie de optiunile nesting (si de multe ori keyctl) activate; daca Docker refuza sa porneasca acolo, cere suportului nostru sa le activeze sau alege un plan KVM.
  • Aproximativ 30 de minute.
Instalare n8n: 7 pasi1234567DNS + VPSufw, updateDocker+ Compose.envsecretecompose.yaml3 serviciiCaddyfileHTTPSPornire stackcont ownerPrimulworkflowAprox. 20-30 de minute pe un VPS nou
Figura 2: procedura in 7 pasi, la fel ca Pasul 1 - Pasul 7 de mai jos. Pasii 1-2 pregatesc serverul, pasii 3-5 scriu configuratia, pasii 6-7 pornesc n8n si il testeaza.

Pasul 1: Configurezi DNS-ul si pregatesti VPS-ul

Creeaza o inregistrare A pentru n8n.example.com care sa indice adresa IPv4 a VPS-ului (si o inregistrare AAAA daca VPS-ul are IPv6 configurat). Fa asta primul lucru, pentru ca la pasul 6 Caddy are nevoie ca numele sa rezolve catre serverul tau cand cere certificatul. Verifica direct de pe VPS:

getent hosts n8n.example.com

Ar trebui sa vezi IP-ul VPS-ului langa nume. Daca nu apare nimic sau apare un IP vechi, mai asteapta cateva minute sa se propage DNS-ul.

Apoi aduci sistemul la zi:

sudo apt update && sudo apt full-upgrade -y

Daca ai nevoie de restart depinde de distributie. Pe Ubuntu, fisierul /var/run/reboot-required apare cand e nevoie de repornire:

[ -f /var/run/reboot-required ] && echo "Trebuie restart" || echo "Nu e nevoie de restart"

Pe Debian fisierul nu este creat mereu, asa ca regula e mai simpla: daca actualizarea a instalat un pachet linux-image nou, reporneste cu sudo reboot.

Optional, poti seta si ceasul serverului pe fusul tau orar cu sudo timedatectl set-timezone Europe/Bucharest. Asta afecteaza doar log-urile gazdei; containerele isi iau fusul orar din configuratia de la pasul 3.

Acum firewall-ul. ufw (“uncomplicated firewall”) e preinstalat dar inactiv pe Ubuntu si nu e instalat pe Debian, deci comanda de instalare nu strica nimic pe niciuna:

sudo apt install -y ufw
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw enable
sudo ufw status verbose

Permite SSH inainte sa activezi firewall-ul, altfel te blochezi singur afara; daca ai mutat SSH pe alt port, permite acel port in loc de 22. Portul 443 e permis atat pe TCP cat si pe UDP, pentru ca, Caddy poate servi si HTTP/3 peste UDP. ufw status verbose ar trebui sa afiseze cele trei reguli ca ALLOW IN.

Un detaliu care ii surprinde pe multi: porturile publicate de Docker ocolesc ufw. Docker isi scrie propriile reguli iptables, deci un container publicat pe un port e accesibil din internet indiferent ce spune ufw. Regulile pentru 80/443 de mai sus documenteaza intentia, dar ufw protejeaza de fapt doar serviciile gazdei, cum e SSH. Exact de asta fisierul nostru Compose nu publica niciodata 5678 (n8n) sau 5432 (PostgreSQL). Retine si ca ufw poate sa nu functioneze intr-un container LXC neprivilegiat, unde te bazezi pe firewall-ul gazdei.

Pasul 2: Instalezi Docker Engine si pluginul Compose

Instalam Docker din repository-ul apt oficial Docker, care ofera un Engine recent si pluginul Compose v2. Daca ai instalat anterior pachetele distributiei docker.io sau docker-compose, sterge-le intai cu sudo apt remove docker.io docker-compose.

sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/${ID}/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/${ID}
Suites: ${VERSION_CODENAME}
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Ce face blocul: citeste /etc/os-release, unde ID este debian sau ubuntu, iar VERSION_CODENAME este trixie pe Debian 13 sau noble pe Ubuntu 24.04. Descarca cheia de semnare Docker, scrie repository-ul in formatul modern deb822 in /etc/apt/sources.list.d/docker.sources si instaleaza pachetele. Acelasi bloc merge pe ambele distributii, pentru ca variabilele completeaza valorile corecte.

Verifica daca totul functioneaza:

sudo docker run --rm hello-world
sudo docker compose version

Ar trebui sa vezi “Hello from Docker!” si o linie de tipul Docker Compose version v2.x.x. In tot ghidul punem sudo in fata comenzilor Docker. Iti poti adauga utilizatorul in grupul docker (sudo usermod -aG docker $USER, apoi te deloghezi si te loghezi din nou), dar tine minte ca apartenenta la acest grup echivaleaza cu acces root.

Pasul 3: Creezi folderul proiectului si generezi secretele

Toate fisierele stack-ului stau intr-un singur folder. Secretele merg intr-un fisier .env, pe care Docker Compose il citeste automat din folderul proiectului:

mkdir -p ~/n8n && cd ~/n8n
cat > .env <<EOF
N8N_DOMAIN=n8n.example.com
GENERIC_TIMEZONE=Europe/Bucharest
POSTGRES_PASSWORD=$(openssl rand -hex 24)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
nano .env

openssl rand -hex genereaza valori aleatoare, asa ca primesti o parola de baza de date de 48 de caractere si o cheie de criptare de 64 de caractere fara sa tastezi nimic. Caracterele hex evita problemele de ghilimele cu simboluri speciale. chmod 600 face fisierul lizibil doar pentru utilizatorul tau. In nano, inlocuieste n8n.example.com cu domeniul tau real si ajusteaza fusul orar daca nu esti in Romania, apoi salvezi cu Ctrl+O, Enter si iesi cu Ctrl+X.

De ce conteaza cheia de criptare

Cand salvezi credentiale in n8n (o cheie API, o parola SMTP, un token OAuth), n8n le cripteaza in baza de date cu N8N_ENCRYPTION_KEY. Daca nu o setezi, n8n genereaza o cheie aleatoare la prima pornire si o pastreaza in volumul lui de date. Merge bine pana in ziua in care pierzi sau recreezi volumul: baza de date e tot acolo, dar fiecare credential salvat devine imposibil de citit. Cand setezi tu cheia, o controlezi tu. Copiaza acum tot fisierul .env in managerul tau de parole. Cheia nu are voie sa se schimbe dupa prima pornire.

De ce conteaza fusul orar

GENERIC_TIMEZONE este fusul orar folosit de n8n pentru nodurile Schedule Trigger si pentru expresii cu date, cum e $now. Valoarea implicita este America/New_York, deci fara ea un workflow programat “in fiecare zi la 08:00” porneste la 08:00 ora New York-ului, adica dupa-amiaza in Bucuresti. Trimitem aceeasi valoare si ca TZ, care seteaza ora din interiorul containerului pentru log-uri. Fiecare workflow isi poate suprascrie fusul orar din setarile proprii.

Pasul 4: Scrii fisierul n8n Docker Compose

Creeaza compose.yaml in acelasi folder (nano compose.yaml) si lipeste:

services:
  postgres:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: "5432"
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_HOST: ${N8N_DOMAIN}
      N8N_PORT: "5678"
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${N8N_DOMAIN}/
      N8N_PROXY_HOPS: "1"
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      TZ: ${GENERIC_TIMEZONE}
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
      EXECUTIONS_DATA_PRUNE: "true"
      EXECUTIONS_DATA_MAX_AGE: "336"
      N8N_DIAGNOSTICS_ENABLED: "false"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment:
      N8N_DOMAIN: ${N8N_DOMAIN}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - n8n

volumes:
  postgres_data:
  n8n_data:
  caddy_data:
  caddy_config:

Pe rand, fiecare serviciu:

  • postgres foloseste imaginea oficiala postgres:17. Variabilele POSTGRES_* creeaza utilizatorul si baza de date la prima pornire, datele stau in volumul postgres_data, iar healthcheck-ul ruleaza pg_isready, ca celelalte servicii sa stie cand baza de date e cu adevarat gata. Nu exista sectiune ports:, deci baza de date e invizibila din internet.
  • n8n foloseste imaginea oficiala din registry-ul propriu n8n. Variabilele DB_* il trimit catre PostgreSQL dupa numele serviciului (Compose da fiecarui serviciu un nume DNS in reteaua interna). N8N_HOST, N8N_PROTOCOL si WEBHOOK_URL ii spun lui n8n adresa lui publica, asa ca URL-urile de webhook afisate sunt https://n8n.example.com/webhook/... si nu http://localhost:5678/.... N8N_PROXY_HOPS=1 spune ca exista exact un reverse proxy in fata, deci n8n are incredere in IP-ul clientului transmis de Caddy. N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS tine privat fisierul de setari. Curatarea executiilor opreste baza de date din a creste la nesfarsit: istoricul mai vechi de 336 de ore (14 zile) se sterge. N8N_DIAGNOSTICS_ENABLED=false opreste telemetria anonima (optional). depends_on cu service_healthy asteapta PostgreSQL inainte sa porneasca n8n.
  • caddy este singurul serviciu cu ports:, publicand 80 si 443 (plus 443/udp pentru HTTP/3). Primeste N8N_DOMAIN ca variabila de mediu, pe care o foloseste Caddyfile-ul de la pasul urmator.

Despre tag-urile imaginilor. postgres:17 si caddy:2 urmaresc o versiune majora: primesti corecturi de bug-uri si patch-uri de securitate, dar nu un salt surpriza la o versiune majora noua. Pentru n8n folosim latest, tag-ul pe care n8n il pune pe versiunea stabila curenta. n8n scoate versiuni noi foarte des, deci cu latest un docker compose pull poate aduce o versiune cu schimbari incompatibile. Dupa ce totul merge, uita-te pe pagina de release-uri n8n si ia in calcul sa inlocuiesti latest cu versiunea exacta pe care ai testat-o, apoi o cresti constient. In general, verifica intotdeauna release-urile fiecarui proiect pentru cea mai noua versiune inainte de deploy.

Valideaza fisierul:

sudo docker compose config --quiet

Daca nu apare nimic, sintaxa e corecta si toate variabilele au fost gasite in .env. Un avertisment de tipul The "N8N_DOMAIN" variable is not set inseamna ca lipseste fisierul .env sau esti in alt folder.

Pasul 5: Configurezi Caddy pentru HTTPS automat

Caddy are nevoie de doar cateva randuri:

cat > Caddyfile <<'EOF'
{$N8N_DOMAIN} {
    reverse_proxy n8n:5678
}
EOF

{$N8N_DOMAIN} este sintaxa Caddy pentru o variabila de mediu, deci domeniul e definit intr-un singur loc, fisierul .env. Ghilimelele din jurul 'EOF' impiedica shell-ul sa interpreteze $ cand scrie fisierul. La pornire, Caddy vede un nume de domeniu real, cere un certificat Let’s Encrypt prin portul 80, redirectioneaza HTTP spre HTTPS si reinnoieste certificatul automat. reverse_proxy transmite si conexiunile websocket, folosite de editorul n8n pentru actualizari live.

Optional: ca sa primesti notificari de expirare de la Let’s Encrypt, adauga un bloc de optiuni globale cu emailul tau chiar la inceputul Caddyfile-ului, inaintea blocului site-ului: { pe un rand, email tu@example.com pe urmatorul, } pe al treilea.

Pasul 6: Pornesti stack-ul si creezi contul de owner

cd ~/n8n
sudo docker compose up -d
sudo docker compose ps

Prima pornire descarca imaginile, ceea ce dureaza un minut sau doua. ps ar trebui sa listeze trei containere cu status Up, iar postgres marcat (healthy). Urmareste log-ul n8n:

sudo docker compose logs -f n8n

Dupa ce se termina migrarile bazei de date, ar trebui sa vezi o linie asemanatoare cu Editor is now accessible via: https://n8n.example.com. Apasa Ctrl+C ca sa nu mai urmaresti log-ul; containerul ramane pornit. Verifica si Caddy cu sudo docker compose logs caddy si cauta certificate obtained successfully. Un test rapid de sanatate, de pe orice calculator:

curl -s https://n8n.example.com/healthz

Raspunsul asteptat este {"status":"ok"}. Mai tarziu poti pune un monitor de uptime pe acest URL.

Deschide https://n8n.example.com in browser. n8n iti cere sa creezi contul de owner: foloseste un email real si o parola lunga si unica. Apoi intra in setarile personale si activeaza autentificarea in doi pasi. Este posibil ca n8n sa iti ofere prin email si o cheie de licenta community gratuita, care deblocheaza cateva functii in plus in editia self-hosted; e optionala.

Pasul 7: Construiesti primul workflow

Prima automatizare este un webhook care primeste datele unui formular de contact, le adauga ora curenta si raspunde cu JSON. Nu are nevoie de conturi externe si demonstreaza lucrul cel mai important: webhook-urile publice prin HTTPS ajung la n8n-ul tau.

Primul workflow: webhook la intrare, JSON la iesirecurl / formularPOST JSONWebhook/contact-formEdit Fieldsnume, email, oraRespondto Webhookraspuns JSON inapoi catre apelant
Figura 3: workflow-ul de la Pasul 7. Trei noduri sunt suficiente ca sa primesti date prin HTTPS, sa le formatezi si sa raspunzi apelantului.
  1. In n8n, apasa Create workflow si adauga trigger-ul On webhook call (nodul Webhook).
  2. Seteaza HTTP Method pe POST, Path pe contact-form si Respond pe “Using ‘Respond to Webhook’ Node”.
  3. Adauga dupa el un nod Edit Fields (Set) cu trei campuri:
    • name = {{ $json.body.name }}
    • email = {{ $json.body.email }}
    • received_at = {{ $now.toISO() }}
  4. Adauga un nod Respond to Webhook si seteaza Respond With pe “First Incoming Item”.
  5. Salveaza workflow-ul.

Expresiile dintre acoladele duble citesc date din nodul anterior: nodul Webhook pune corpul cererii sub body, iar $now este ora curenta in fusul tau GENERIC_TIMEZONE.

Nodul Webhook afiseaza doua URL-uri. URL-ul de test (/webhook-test/contact-form) functioneaza doar cat timp apesi “Listen for test event” (sau “Execute workflow”) in editor si iti arata datele live pe canvas. Apasa butonul, apoi trimite o cerere din terminal:

curl -s -X POST https://n8n.example.com/webhook-test/contact-form \
  -H "Content-Type: application/json" \
  -d '{"name":"Ana","email":"ana@example.com"}'

Rezultatul asteptat, cu ora in fusul tau orar (observa decalajul +02:00 pentru Bucuresti in noiembrie):

{"name":"Ana","email":"ana@example.com","received_at":"2026-11-10T10:15:42.123+02:00"}

URL-ul de productie (/webhook/contact-form) functioneaza doar dupa ce activezi workflow-ul din comutatorul din dreapta sus a editorului (versiunile mai noi de n8n numesc acest pas “Publish”). Activeaza-l, apoi repeta comanda curl cu /webhook/ in loc de /webhook-test/. Executia apare acum in lista Executions, nu pe canvas.

De aici il poti extinde: un nod Google Sheets care noteaza fiecare mesaj, un nod Send Email sau Telegram care te anunta, ori un nod IF care trimite la contabilitate mesajele ce contin cuvantul “factura”. Inlocuieste Webhook-ul cu un Schedule Trigger si ai un cron job cu editor vizual.

Cat server ii trebuie lui n8n?

Consum aproximativ de RAM (MB)0100200300400500600700RAM (MB)~30~90~220~600~340CaddyPostgreSQLn8n inactivn8n incarcatStack inactivComponenta
Figura 4: consumul aproximativ de memorie pe componenta. "Incarcat" inseamna n8n procesand volume mari de date; valorile reale depind de workflow-urile tale si de versiunea n8n.

n8n este o aplicatie Node.js. In repaus, cu cateva workflow-uri, tot stack-ul foloseste in jur de 350 MB de RAM. Memoria creste in timpul executiilor care lucreaza cu volume mari de date: raspunsuri JSON uriase, tabele cu mii de randuri sau fisiere binare precum PDF-uri si imagini. Un VPS de 2 GB lasa loc suficient pentru automatizarile tipice ale unei firme mici; cu 1 GB, adauga swap si evita procesarea fisierelor mari. Procesorul sta aproape liber in afara executiilor, deci 1-2 vCPU ajung pentru majoritatea. Daca ajungi la sute de executii pe minut, urmatorul pas este queue mode (Redis plus containere worker separate), iar acolo se vede castigul de a fi pornit direct pe PostgreSQL.

Backup si restaurare

Trebuie salvate trei lucruri: baza de date PostgreSQL (workflow-uri, credentiale, istoric), volumul n8n_data (setari si eventuale date binare pe disc) si fisierul .env, mai ales cheia de criptare. Fara cheie, un backup al bazei de date e util doar pe jumatate, pentru ca credentialele salvate nu mai pot fi decriptate.

mkdir -p ~/n8n-backups
cd ~/n8n
sudo docker compose exec -T postgres pg_dump -U n8n -d n8n -Fc > ~/n8n-backups/n8n-db-$(date +%F).dump

-Fc creeaza formatul custom comprimat al PostgreSQL, pe care il restaurezi cu pg_restore. Nu ai nevoie de parola, pentru ca pg_dump ruleaza in container prin socket-ul local, in care imaginea oficiala are incredere. Daca te conectezi vreodata prin retea de pe alt calculator, trimite parola prin variabila de mediu PGPASSWORD sau printr-un fisier ~/.pgpass, niciodata ca argument in linia de comanda, unde e vizibila in lista de procese.

Urmeaza volumul n8n. Compose prefixeaza numele volumelor cu numele folderului proiectului, deci al nostru se numeste n8n_n8n_data (verifica cu sudo docker volume ls):

sudo docker run --rm -v n8n_n8n_data:/data:ro -v ~/n8n-backups:/backup alpine \
  tar czf /backup/n8n-data-$(date +%F).tar.gz -C /data .
sudo chown "$USER": ~/n8n-backups/n8n-data-*.tar.gz
cp ~/n8n/.env ~/n8n/compose.yaml ~/n8n/Caddyfile ~/n8n-backups/

Arhiva e scrisa de root intr-un container temporar, asa ca chown o trece inapoi pe utilizatorul tau inainte s-o copiezi de pe server. Iar un backup care sta doar pe acelasi server nu e backup: trage-l pe alt calculator, de exemplu de pe PC-ul tau cu rsync -av user@vps-ul-tau:n8n-backups/ ./n8n-backups/.

Ca sa automatizezi dump-ul bazei de date, deschide crontab-ul lui root cu sudo crontab -e si adauga o linie ca cea de mai jos (inlocuieste utilizator; in crontab semnul % trebuie scris \%). Face dump in fiecare noapte la 03:00 si sterge dump-urile mai vechi de 14 zile:

0 3 * * * cd /home/utilizator/n8n && docker compose exec -T postgres pg_dump -U n8n -d n8n -Fc > /home/utilizator/n8n-backups/n8n-db-$(date +\%F).dump && find /home/utilizator/n8n-backups -name 'n8n-db-*.dump' -mtime +14 -delete

Aceste fisiere sunt detinute de root, deci ruleaza sudo chown pe ele inainte sa le copiezi, ca mai sus.

Restaurarea. Restaurezi intotdeauna intr-o baza de date goala, niciodata peste cea activa, pe care n8n a migrat-o si a umplut-o deja. Opresti n8n, recreezi baza de date, restaurezi, apoi pornesti n8n din nou:

cd ~/n8n
sudo docker compose stop n8n
sudo docker compose exec -T postgres dropdb -U n8n --if-exists n8n
sudo docker compose exec -T postgres createdb -U n8n -O n8n n8n
sudo docker compose exec -T postgres pg_restore -U n8n -d n8n --no-owner < ~/n8n-backups/n8n-db-2026-11-10.dump
sudo docker compose start n8n

Fisierul .env trebuie sa contina aceeasi N8N_ENCRYPTION_KEY ca in momentul backup-ului. Pe un server complet nou, copiezi intai .env, compose.yaml si Caddyfile, pornesti doar baza de date cu sudo docker compose up -d postgres (care creeaza o baza n8n noua si goala), restaurezi dump-ul in ea, despachetezi arhiva n8n-data in noul volum cu acelasi truc alpine (tar xzf in loc de tar czf) si abia apoi rulezi sudo docker compose up -d.

Actualizarea n8n in siguranta

cd ~/n8n
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune -f

pull descarca imagini mai noi pentru tag-urile din fisierul Compose, up -d recreeaza doar containerele a caror imagine s-a schimbat, iar prune sterge imaginile vechi. Inainte de fiecare actualizare, fa un backup al bazei de date si citeste notele de release, mai ales inaintea unei versiuni majore (de exemplu de la 1.x la 2.x), care poate elimina functii sau noduri depreciate. n8n isi ruleaza singur migrarile de baza de date la pornire, asa ca urmareste sudo docker compose logs -f n8n dupa update si citeste eventualele avertismente de depreciere.

PostgreSQL e alta poveste: schimbarea din postgres:17 in postgres:18 nu este un simplu update. Versiunile majore PostgreSQL folosesc formate de date incompatibile, iar noul container va refuza sa porneasca pe volumul vechi. Calea sigura este: dump, volum nou si gol cu noua versiune, restaurare. Sa ramai pe 17 cu pull regulat e perfect in regula ani de zile.

Depanare: greseli frecvente

URL-urile de webhook arata http://localhost:5678. Lipseste WEBHOOK_URL sau containerul nu a fost recreat dupa ce ai editat-o. Corecteaza variabila si ruleaza sudo docker compose up -d; Compose recreeaza containerele a caror configuratie s-a schimbat.

Caddy nu poate obtine certificatul. Citeste sudo docker compose logs caddy. Cauze frecvente: inregistrarea DNS nu indica inca spre acest VPS, portul 80 e blocat undeva inainte de server, sau alt server web (Apache, nginx) de pe gazda ocupa deja porturile 80/443, ceea ce apare ca “address already in use” la pornirea stack-ului. Let’s Encrypt limiteaza si incercarile esuate repetate, deci rezolva cauza inainte sa repornesti in bucla.

“Webhook not registered” (404). Ai apelat URL-ul de test fara sa apesi “Listen for test event”, sau URL-ul de productie cand workflow-ul e inactiv. URL-urile de test sunt pentru editor, cele de productie pentru workflow-uri active.

“Mismatching encryption keys”. Volumul n8n_data contine un fisier de configurare cu alta cheie decat N8N_ENCRYPTION_KEY, de obicei pentru ca n8n a fost pornit o data inainte sa setezi variabila. La o instalare noua, fara date importante, sterge stack-ul impreuna cu volumele (sudo docker compose down -v, care sterge toate datele) si porneste din nou. Altfel, pune la loc cheia originala in .env.

Programarile pornesc la ora gresita. GENERIC_TIMEZONE nu e setata sau workflow-ul are propriul fus orar in setari. Retine ca doar TZ nu schimba fusul orar al programarilor in n8n.

Erori despre X-Forwarded-For sau “trust proxy”. Asigura-te ca N8N_PROXY_HOPS este 1 cand ai un singur proxy in fata lui n8n.

PostgreSQL respinge parola dupa ce ai schimbat-o in .env. POSTGRES_PASSWORD se foloseste doar la prima initializare a volumului de date. Ca s-o schimbi ulterior, deschide sudo docker compose exec postgres psql -U n8n, ruleaza \password n8n (iti cere noua parola), apoi actualizeaza .env si ruleaza sudo docker compose up -d.

Docker nu porneste intr-un container LXC. LXC neprivilegiat are nevoie de nesting si keyctl activate de gazda. Pe KVM problema nu exista.

Erori de permisiuni pe /home/node/.n8n. n8n ruleaza ca utilizatorul node (UID 1000). Cu un volum numit, ca in fisierul nostru, Docker se ocupa de asta. Daca ai trecut pe bind mount (un folder de pe gazda), ruleaza sudo chown -R 1000:1000 pe acel folder.

Note de securitate

  • Publica doar Caddy. Porturile publicate de Docker ocolesc ufw, deci singura protectie reala pentru n8n si PostgreSQL e sa nu le publici deloc porturile. Nu adauga "5678:5678" “doar pentru test” ca sa-l uiti apoi acolo.
  • Trateaza accesul la editor ca acces la server. Nodul Code ruleaza JavaScript (si Python) pe serverul tau, iar workflow-urile tin credentiale pentru celelalte sisteme ale tale. Da conturi de editor doar oamenilor de incredere, cu parole puternice si autentificare in doi pasi.
  • Protejeaza webhook-urile. Un URL de webhook de productie e public. Pentru orice scrie date, seteaza Authentication pe nodul Webhook (Header Auth cu un token lung si aleator e simplu) sau verifica semnaturile trimise de servicii precum Stripe si GitHub.
  • Limiteaza nodurile riscante. Daca nu ai nevoie de noduri care ruleaza comenzi shell sau lucreaza cu fisierele serverului, cum e Execute Command, blocheaza-le cu variabila de mediu NODES_EXCLUDE. Versiunile recente n8n dezactiveaza deja unele dintre ele implicit; verifica documentatia pentru versiunea ta.
  • Pazeste fisierul .env. Pastreaza-l cu chmod 600, nu-l pune niciodata in Git si tine o copie offline a cheii de criptare.
  • Actualizeaza regulat. Uneltele de automatizare tin cheile catre multe alte sisteme, ceea ce le face tinte atractive. Urmareste release-urile si aplica rapid corecturile de securitate.

Deci, merita n8n self-hosted?

Daca automatizezi mai mult decat cateva sarcini marunte, raspunsul este de obicei da. Un VPS modest ruleaza confortabil n8n cu PostgreSQL, executiile sunt nelimitate, iar datele raman pe serverul tau, nu in cloud-ul altcuiva. Pretul este putina disciplina operationala: pastrezi cheia de criptare in siguranta, faci backup la baza de date in afara serverului, citesti notele de release inainte de update. Urmeaza cei sapte pasi de mai sus si ai in jur de o jumatate de ora un setup gata de productie, cu webhook-uri HTTPS reale pe care serviciile externe le pot apela.

Poti porni acest stack chiar azi pe un VPS din data center-ul nostru din Romania, sau poti lasa echipa noastra de administrare servere sa instaleze, securizeze, salveze si actualizeze n8n pentru tine, ca tu sa te ocupi de automatizari, nu de intretinerea serverelor.

Intrebari frecvente

Este n8n gratuit in varianta self-hosted?

Da. Editia Community poate fi gazduita gratuit pentru uz intern in firma si pentru uz personal, sub Sustainable Use License; platesti doar serverul. Unele functii enterprise cer licenta platita, iar n8n in sine nu poate fi revandut ca serviciu gazduit.

Pot rula n8n pe un VPS cu 1 GB RAM?

Pentru cateva workflow-uri usoare, da, mai ales cu swap activat. Daca procesezi fisiere mari sau multe executii in paralel, 2 GB este minimul confortabil.

Pot folosi nginx sau Traefik in loc de Caddy?

Da. Orice reverse proxy merge, atata timp cat transmite conexiunile websocket si header-ele de forwarding, iar N8N_PROXY_HOPS este setat la numarul de proxy-uri din fata lui n8n. Am ales Caddy pentru ca se ocupa de certificate fara nicio configurare.

Am pornit cu SQLite. Pot trece pe PostgreSQL?

Da. Cea mai curata cale este sa exporti workflow-urile si credentialele cu CLI-ul n8n (n8n export:workflow si n8n export:credentials) din instanta veche, apoi sa le importi in noua instanta pe PostgreSQL, care trebuie sa foloseasca aceeasi cheie de criptare.

Avem Incredere & Suntem Membri

Suntem membri ai principalelor organizatii de infrastructura internet.

RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX