Self-host do n8n em VPS: guia completo em portugues (Docker + Traefik + Postgres) e os 5 erros que quase todo mundo comete

Boa parte do conteudo de self-hosting do n8n esta em ingles, e o que existe em portugues normalmente para no docker run. Esse post e o passo a passo completo de uma instalacao que aguenta producao, mais os erros que eu vejo com mais frequencia.

Testado em Ubuntu 22.04 e 24.04.

O que vamos montar

  • n8n em Docker, atras do Traefik com HTTPS automatico (Let’s Encrypt)
    • Postgres como banco (nao SQLite, explico o porque no erro numero 1)
      • Reinicio automatico, backup, e um caminho de upgrade que nao perde dados
    • Requisito minimo realista: 2 GB de RAM e 2 vCPUs. Da para subir com 1 GB, mas o primeiro workflow com um no de IA ou um Code node mais pesado derruba por OOM. Se voce pretende rodar n8n junto com outra coisa no mesmo servidor, 4 GB.
  • Passo 1 - DNS

Antes de tudo, aponte um subdominio para o IP da VPS:


n8n.seudominio.com.br   A   IP_DA_SUA_VPS
```

Espere propagar. O Let's Encrypt vai falhar se o DNS ainda nao resolver, e o erro que ele devolve nao e obvio.

## Passo 2 - Docker

```bash
curl -fsSL https://get.docker.com | sh
docker compose version
```

## Passo 3 - A stack

```bash
mkdir -p /root/n8n && cd /root/n8n
```

`docker-compose.yml`:

```yaml
services:
  traefik:
    image: traefik:v3
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt

  postgres:
    image: postgres:16
    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"]
      interval: 10s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_HOST: ${N8N_HOST}
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${N8N_HOST}/
      GENERIC_TIMEZONE: America/Sao_Paulo
      TZ: America/Sao_Paulo
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
    volumes:
      - n8n_data:/home/node/.n8n
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`${N8N_HOST}`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls.certresolver=letsencrypt
      - traefik.http.services.n8n.loadbalancer.server.port=5678

volumes:
  letsencrypt:
  postgres_data:
  n8n_data:
```

`.env` no mesmo diretorio:

```bash
cat > .env <<EOF
N8N_HOST=n8n.seudominio.com.br
ACME_EMAIL=voce@seudominio.com.br
POSTGRES_PASSWORD=$(openssl rand -hex 24)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
```

Sobe:

```bash
docker compose up -d
docker compose logs -f n8n
```

Em um ou dois minutos, `https://n8n.seudominio.com.br` responde com certificado valido.

---

# Os 5 erros

## 1. Deixar o SQLite em producao

O n8n sobe com SQLite por padrao e funciona bem, ate voce ter execucoes concorrentes. Ai comecam os `database is locked`, execucoes que somem, e lentidao crescente conforme a tabela de execucoes engorda.

Migrar depois e chato. Comece com Postgres. E a diferenca de um servico a mais no compose.

## 2. Nao guardar a N8N_ENCRYPTION_KEY

Essa chave criptografa todas as credenciais que voce cadastra no n8n. Se voce perder o volume e nao tiver a chave, todas as credenciais viram lixo ilegivel, mesmo com backup do banco. Voce vai recadastrar tudo, uma a uma.

Guarde a chave num gerenciador de senhas, hoje, antes de continuar lendo.

## 3. Esquecer a WEBHOOK_URL

Sem essa variavel, o n8n gera as URLs de webhook usando o hostname interno do container. O painel mostra uma URL que nao funciona de fora, e voce perde meia hora achando que o problema e no servico que esta chamando.

Se esta atras de proxy, WEBHOOK_URL e obrigatoria.

## 4. Nunca configurar o pruning de execucoes

A tabela execution_entity cresce sem parar. Em uma instancia com workflows rodando de minuto em minuto, sao milhoes de linhas em poucos meses. O banco incha, o disco enche, o n8n fica lento.

Duas variaveis resolvem:

```yaml
EXECUTIONS_DATA_PRUNE: "true"
EXECUTIONS_DATA_MAX_AGE: "336"   # horas - 14 dias
```

## 5. Expor a porta 5678

`ports: - "5678:5678"` no servico do n8n coloca o painel na internet sem TLS e sem passar pelo proxy. Um n8n aberto e acesso as suas credenciais e execucao de codigo arbitrario via Code node.

No compose acima o n8n nao publica porta nenhuma. Quem fala com a internet e so o Traefik. Mantenha assim.

Complemente com firewall:

```bash
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable
```

---

## Backup

O minimo que salva a sua noite:

```bash
docker compose exec -T postgres pg_dump -U n8n n8n | gzip > n8n-$(date +%F).sql.gz
```

Isso mais a N8N_ENCRYPTION_KEY guardada em outro lugar = recuperacao completa.

## Upgrade

```bash
cd /root/n8n
docker compose pull
docker compose up -d
```

Faca o dump antes. Sempre.

---

Escrevi uma versao mais longa disso em portugues no meu blog, com a parte de monitoramento e as configuracoes de fila para quem precisa de worker separado: https://yousecure.io/blog/n8n-self-hosted-instalando-automacao-vps-ubuntu

Se travar em algum passo, comenta ai que eu respondo.