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.
Neste artigo
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 statusO 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.
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 updateInstale 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 versionExiste 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 ~/n8nSegredos 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 32Rode o comando três vezes e cole os resultados no .env:
N8N_VERSION=2.47.4
N8N_DOMAIN=n8n.example.com
[email protected]
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=n8nchmod 600 .envN8N_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.
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 ACom o DNS resolvendo, suba a stack:
cd ~/n8n
sudo docker compose up -d
sudo docker compose psO 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 caddySe 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/healthzAbra 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/.n8nO 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.
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 n8nO 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.