Este guia monta o n8n self-hosted para produção leve numa VPS Ubuntu 24.04: o n8n com PostgreSQL, o task runner num contêiner separado (como a documentação do n8n recomenda para produção) e o Caddy na frente, emitindo e renovando o certificado HTTPS. Tudo roda com Docker Compose a partir de três arquivos, e o guia também cobre atualização e backup.
O que você vai precisar
- Uma VPS com Ubuntu 24.04 de 64 bits e acesso SSH com um usuário que tenha
sudo. - Um domínio em que você consiga criar registros DNS. Os exemplos usam
n8n.example.com. - Um e-mail para a conta ACME do certificado.
Quanto ao tamanho da máquina, a documentação do n8n não fixa um mínimo para este cenário. O guia oficial de Docker Compose pede 2 vCPU e 4 GB de RAM quando a instalação inclui o sandbox do n8n Assistant. Aqui são quatro contêineres (Caddy, n8n, runner e PostgreSQL), então trate 2 vCPU e 4 GB como piso. Com 4 vCPU e 8 GB sobra espaço para execuções com mais dados e para o banco crescer.
Se você só quer experimentar o n8n, a Nuvits também entrega a VPS com o n8n pré-instalado na contratação. Nessa opção, o n8n roda num único contêiner, sem PostgreSQL nem runner externo, e só responde dentro da VPS: o editor abre por um túnel SSH. Para receber webhooks da internet com domínio e HTTPS, siga este guia.
Preparar o servidor
Se o provedor entrega a VPS com acesso como root, crie um usuário com sudo e copie a sua chave SSH para ele:
adduser deploy
usermod -aG sudo deploy
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy ~/.ssh/authorized_keys /home/deploy/.ssh/
Abra outra sessão como deploy e confira que o login funciona antes de fechar a de root. Depois, libere SSH, HTTP e HTTPS no ufw e ative o firewall:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw enable
sudo ufw status
O ufw enable pede confirmação porque pode derrubar a conexão SSH; com a regra da porta 22 já criada, responda y. A regra 443 sem protocolo libera TCP e UDP. O UDP serve ao HTTP/3, que o Caddy também atende.
O Docker passa por cima do ufw. Uma porta publicada com
ports:no Compose fica acessível de fora mesmo sem regra no ufw, porque o Docker desvia o tráfego antes que ele chegue às regras do firewall. Por isso, neste guia só o Caddy publica portas (80 e 443). O n8n e o PostgreSQL ficam apenas na rede interna do Compose.
Instalar o Docker
Na Nuvits, dá para pedir a VPS com o Docker já instalado na contratação. Nesse caso, pule para o docker compose version no fim desta seção.
O caminho que a documentação do Docker indica para servidores é o repositório apt oficial. Primeiro, remova pacotes não oficiais que possam conflitar (numa instalação limpa, o apt costuma responder que não há nada para remover):
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)
Adicione o repositório e a chave GPG do Docker:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/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 <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
Instale o Docker Engine e o plugin do Compose, depois teste:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-world
docker compose version
Existe também o script get.docker.com, mas a própria documentação do Docker o indica só para ambientes de teste e desenvolvimento, não para produção.
Os comandos deste guia usam sudo docker. Você pode adicionar seu usuário ao grupo docker para dispensar o sudo, mas a documentação do Docker avisa que esse grupo dá privilégios equivalentes aos de root.
Configurar o n8n
Crie uma pasta para o projeto. O Compose usa o nome dela como prefixo de volumes e contêineres:
mkdir -p ~/n8n && cd ~/n8n
Segredos no .env
Gere três valores aleatórios, um para a chave de criptografia, um para o token do runner e um para a senha do banco:
openssl rand -hex 32
Rode o comando três vezes e cole os resultados no .env:
N8N_VERSION=2.47.4
N8N_DOMAIN=n8n.example.com
ACME_EMAIL=voce@example.com
GENERIC_TIMEZONE=America/Sao_Paulo
N8N_ENCRYPTION_KEY=cole-aqui-o-primeiro-valor
N8N_RUNNERS_AUTH_TOKEN=cole-aqui-o-segundo-valor
POSTGRES_USER=n8n
POSTGRES_PASSWORD=cole-aqui-o-terceiro-valor
POSTGRES_DB=n8n
chmod 600 .env
N8N_VERSION fixa a versão das imagens do n8n e do runner, que precisam ser iguais. Use a versão stable mais recente listada nas releases do n8n; 2.47.4 era a estável quando este texto foi escrito.
Guarde a chave. O n8n usa
N8N_ENCRYPTION_KEYpara criptografar as credenciais salvas no banco. Sem ela, um backup restaurado abre, mas nenhuma credencial pode ser lida. Guarde uma cópia do.envfora da VPS, num gerenciador de senhas, por exemplo.
Caddyfile
O Caddy obtém o certificado sozinho quando encontra um nome de domínio no endereço do site. O flush_interval -1 desliga o buffer da resposta, como no exemplo oficial do n8n com Caddy:
{
email {$ACME_EMAIL}
}
{$N8N_DOMAIN} {
reverse_proxy n8n:5678 {
flush_interval -1
}
}
O Caddy substitui {$N8N_DOMAIN} e {$ACME_EMAIL} pelas variáveis de ambiente do contêiner, que o Compose preenche a partir do .env. O reverse_proxy já envia os cabeçalhos X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host, que o n8n precisa receber atrás de um proxy.
docker-compose.yml
services:
caddy:
image: caddy:2.11
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
- N8N_DOMAIN=${N8N_DOMAIN}
- ACME_EMAIL=${ACME_EMAIL}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- n8n
postgres:
image: postgres:18
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- PGDATA=/var/lib/postgresql/data
volumes:
- db_storage:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
n8n:
image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
restart: always
environment:
- N8N_HOST=${N8N_DOMAIN}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- N8N_WEBHOOK_URL=https://${N8N_DOMAIN}/
- N8N_PROXY_HOPS=1
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- N8N_RUNNERS_MODE=external
- N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
n8n-runner:
image: n8nio/runners:${N8N_VERSION}
restart: always
environment:
- N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
depends_on:
- n8n
volumes:
caddy_data:
caddy_config:
db_storage:
n8n_data:
O que vale saber sobre esse arquivo:
-
N8N_WEBHOOK_URLeN8N_PROXY_HOPS=1fazem o n8n montar as URLs de webhook com o domínio público e confiar nos cabeçalhos do Caddy. Sem isso, o editor mostra URLs com a porta interna 5678. Em versões anteriores à 2.30.0, a variável se chamaWEBHOOK_URL, hoje obsoleta. - O runner externo executa o código dos nós Code fora do processo do n8n. O modo interno, que é o padrão, está obsoleto e a documentação o desaconselha em produção: código que escape do sandbox fica com o mesmo acesso do n8n, inclusive às credenciais.
- O
PGDATAfixo mantém os dados do Postgres 18 dentro do volume. Não remova essa linha: sem ela, o Postgres 18 grava fora do volume e o banco começa vazio. - O n8n continua com o volume em
/home/node/.n8nmesmo usando PostgreSQL. A documentação recomenda manter essa pasta persistente. - Nenhuma porta do n8n ou do Postgres é publicada. O Caddy alcança o n8n pelo nome do serviço na rede do Compose.
Apontar o DNS e subir
No painel DNS do seu domínio, crie um registro A com o nome n8n (ou o subdomínio escolhido) apontando para o IPv4 da VPS. Antes de subir, confira se o nome já resolve para o IP certo:
dig +short n8n.example.com A
Com o DNS resolvendo, suba a stack:
cd ~/n8n
sudo docker compose up -d
sudo docker compose ps
O n8n só sobe depois que o Postgres passa no healthcheck. Acompanhe os logs do n8n e do Caddy até ver o editor disponível e o certificado emitido:
sudo docker compose logs -f n8n
sudo docker compose logs caddy
Se o certificado não sair, confira se o DNS já propagou e se as portas 80 e 443 também estão abertas no firewall do provedor, quando houver um. Depois de corrigir, rode sudo docker compose restart caddy.
Por fim, teste de fora da VPS:
curl -fsS https://n8n.example.com/healthz
Abra https://n8n.example.com no navegador e crie a conta de owner. Para conferir os webhooks, adicione um nó Webhook a um workflow: a URL de produção deve começar com https://n8n.example.com/webhook/.
Atualizar e fazer backup
Backup
Um backup completo do n8n com PostgreSQL tem duas partes: o banco e a pasta .n8n, que guarda a configuração e a chave de criptografia. Com a stack no ar:
cd ~/n8n
set -a && . ./.env && set +a
sudo docker compose exec -T postgres pg_dumpall -U "$POSTGRES_USER" > n8n-db-$(date +%F).sql
grep -c "PostgreSQL database cluster dump complete" n8n-db-$(date +%F).sql
sudo docker run --rm --volumes-from "$(sudo docker compose ps -q n8n)" -v "$PWD":/backup ubuntu:24.04 tar czf /backup/n8n-data-$(date +%F).tar.gz /home/node/.n8n
O grep precisa imprimir 1. Se imprimir 0, o dump ficou incompleto. Depois, copie os dois arquivos e o .env para fora da VPS, a partir da sua máquina:
mkdir -p backups-n8n
scp 'deploy@IP_DA_VPS:n8n/n8n-*' 'deploy@IP_DA_VPS:n8n/.env' ./backups-n8n/
Um backup guardado só no disco da própria VPS se perde junto com ela. Para restaurar, a ordem é parar o n8n, devolver a pasta .n8n e o banco e subir de novo com o mesmo .env. A página Back up and restore detalha cada passo.
Nunca rode
docker compose down -vnesta pasta. O-vapaga também os volumes, incluindo on8n_datacom a chave de criptografia e odb_storagecom o banco.
Atualizar
O n8n lança uma versão nova quase toda semana. A documentação sugere atualizar pelo menos uma vez por mês, para não pular muitas versões de uma vez, e ler as notas de versão antes. O roteiro:
- Faça o backup acima.
- Troque
N8N_VERSIONno.envpela nova versãostable. O runner acompanha, porque usa a mesma variável. - Baixe as imagens e recrie os contêineres:
cd ~/n8n
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker compose logs -f n8n
O n8n aplica as migrações do banco ao iniciar. Trocar a tag de volta não desfaz essas migrações, e é por isso que o backup vem antes. O docker compose pull também traz a última versão 2.11 do Caddy e a última versão 18 do PostgreSQL. Mudar o Postgres de versão principal (de 18 para 19, por exemplo) exige dump e restauração, não só trocar a tag.
Próximos passos
- Ative a autenticação em dois fatores na conta de owner e configure SMTP para convites e redefinição de senha, com as variáveis de user management.
- Agende o backup com
crone mande as cópias para outro lugar automaticamente. - Separe um usuário do Postgres sem privilégios de superusuário para o n8n, como no exemplo withPostgres do repositório oficial.
- Leia o guia de hardening dos task runners se mais pessoas forem editar workflows na instância.
Top comments (0)