Guia
Como fazer deploy de Node.js com PM2 em VPS
Aprenda a publicar uma aplicação Node.js com PM2 em uma VPS, configurar Nginx, reinício automático, logs e testes seguros em produção com validação.
Tempo estimado: 30-40 minutos
Neste guia, você vai publicar uma aplicação Node.js em uma VPS Ubuntu usando PM2 como gerenciador de processos e Nginx como proxy reverso. A ideia é sair de um processo manual, iniciado com node server.js, para um serviço que reinicia sozinho, gera logs, sobrevive a reboot e responde pela porta 80. O exemplo usa uma API Express simples para que todos os comandos sejam testáveis, mas você pode adaptar o mesmo fluxo para uma aplicação real.
Se você ainda está planejando a infraestrutura da sua API, veja também o guia sobre VPS para API em produção. Para ambientes de desenvolvimento, automações e pipelines, o conteúdo sobre VPS para desenvolvedores ajuda a entender requisitos práticos antes do deploy.
O que você vai aprender
Ao final deste guia, você terá um deploy Node.js funcional e pronto para ser revisado em produção. Você vai aprender a:
- Preparar uma VPS Ubuntu para rodar aplicações Node.js com um usuário não root.
- Instalar Node.js 20, npm, Git, Nginx e PM2 com comandos verificáveis.
- Criar uma aplicação Express simples, ou adaptar o fluxo para seu projeto existente.
- Executar a aplicação com PM2, salvar a lista de processos e habilitar reinício após reboot.
- Configurar Nginx para encaminhar tráfego HTTP da porta 80 para a aplicação na porta 3000.
- Verificar saúde da API com
curl,pm2 status,systemctle logs em tempo real. - Resolver erros comuns, como porta ocupada, processo offline, falha de proxy e variáveis de ambiente ausentes.
O PM2 não substitui testes, observabilidade ou deploy automatizado, mas resolve uma parte essencial do trabalho: manter o processo Node.js vivo e controlado. Ele permite reiniciar, recarregar, consultar logs e restaurar processos após reinicialização do servidor. O Nginx entra na frente para receber conexões HTTP, lidar melhor com clientes externos e isolar a aplicação em uma porta local.
Pré-requisitos
Antes de começar, tenha uma VPS com Ubuntu 22.04 ou 24.04, acesso SSH com um usuário com sudo e pelo menos 1 GB de RAM. O guia também funciona em Debian recente com pequenas adaptações nos pacotes. Em CentOS, AlmaLinux ou Rocky Linux, os comandos de instalação mudam porque o gerenciador de pacotes é dnf ou yum, não apt.
Você também precisa de conhecimentos básicos de terminal: navegar entre diretórios, editar arquivos simples e interpretar saídas de comandos. Não precisa conhecer PM2 previamente. Vamos instalar e validar cada parte.
Use estes dados como referência no guia:
- Usuário de deploy:
deploy - Diretório da aplicação:
/var/www/melhorvps-api - Nome do processo no PM2:
melhorvps-api - Porta interna da aplicação:
3000 - IP de exemplo da VPS:
203.0.113.10
Substitua o IP pelo endereço público da sua VPS ao testar de fora. O IP acima é usado apenas para deixar os comandos concretos. Se você está escolhendo uma máquina para APIs, o comparativo de cloud server para APIs e automações pode ajudar no dimensionamento, mas o procedimento abaixo é neutro em relação ao provedor.
Antes de alterar arquivos de Nginx ou aplicações existentes, faça backup. O guia mostra comandos de cópia antes de mudanças críticas. Evite executar comandos como rm -f em caminhos diferentes dos indicados.
Passo 1: Preparar a VPS e validar o acesso SSH
Comece conectando na VPS. Se você ainda usa root, entre como root apenas para criar um usuário de deploy. Depois, trabalhe com o usuário normal. Isso reduz o risco de alterações acidentais no sistema.
ssh [email protected]
Na primeira conexão, o SSH pode pedir confirmação da chave do servidor:
The authenticity of host '203.0.113.10' can't be established.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Digite yes se o IP estiver correto. Em seguida, crie o usuário deploy e adicione ao grupo sudo:
adduser deploy --disabled-password --gecos ''
usermod -aG sudo deploy
Você deve ver uma saída parecida com:
Adding user 'deploy' ...
Adding new group 'deploy' ...
Creating home directory '/home/deploy' ...
Adding user 'deploy' to group 'sudo' ...
Configure uma senha para o usuário, caso você ainda não tenha chave SSH instalada:
passwd deploy
Agora saia do root e entre como deploy:
exit
ssh [email protected]
Verifique se o usuário consegue executar comandos administrativos:
sudo whoami
A saída esperada é:
root
Atualize a lista de pacotes e aplique atualizações disponíveis. Esse comando pode alterar serviços do sistema, então execute em uma janela de manutenção se a VPS já hospeda algo em produção.
sudo apt update && sudo apt upgrade -y
No final, você deve ver algo como:
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Se houver pacotes atualizados, tudo bem. Antes de seguir, confira o sistema operacional:
lsb_release -a
Saída esperada em Ubuntu:
Distributor ID: Ubuntu
Description: Ubuntu 24.04 LTS
Release: 24.04
Codename: noble
Passo 2: Instalar Node.js, npm, Git e Nginx
Agora instale as ferramentas que o deploy precisa. Vamos usar Node.js 20 via NodeSource, Git para obter ou versionar o projeto, Nginx como proxy reverso e utilitários básicos para testes. Se você já tem Node instalado, valide a versão antes de substituir.
node -v || true
npm -v || true
Se o comando retornar command not found, siga com a instalação. Primeiro, instale dependências de repositório:
sudo apt install -y ca-certificates curl gnupg git nginx
Saída esperada resumida:
Reading package lists... Done
Building dependency tree... Done
Setting up nginx ...
Setting up git ...
Instale o repositório do Node.js 20:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
Você deve ver linhas indicando que o repositório foi configurado:
2026-07-13 00:00:00 - Installing pre-requisites
2026-07-13 00:00:00 - Repository configured successfully.
Instale o Node.js:
sudo apt install -y nodejs
Valide as versões:
node -v
npm -v
git --version
nginx -v
Uma saída saudável fica parecida com:
v20.19.0
10.8.2
git version 2.43.0
nginx version: nginx/1.24.0 (Ubuntu)
Instale o PM2 globalmente com npm:
sudo npm install -g pm2
Output esperado:
added 130 packages in 8s
Confira se o binário está disponível:
pm2 -v
Você deve ver um número de versão, por exemplo:
5.4.3
Por fim, confirme se o Nginx está ativo. Ainda não vamos configurar o proxy, apenas validar que o serviço existe.
sudo systemctl status nginx --no-pager
A saída esperada inclui:
Active: active (running)
Se o Nginx não iniciar, não avance para o proxy. Resolva primeiro o erro de instalação ou conflito de porta.
Passo 3: Preparar a aplicação Node.js no servidor
Neste passo, você vai criar uma aplicação Express mínima para testar o deploy. Se você já tem um projeto, crie um backup antes de mexer no diretório. O comando abaixo só copia se a pasta existir.
if [ -d /var/www/melhorvps-api ]; then sudo cp -a /var/www/melhorvps-api /var/www/melhorvps-api.bak.$(date +%F-%H%M%S); fi
Crie o diretório da aplicação e entregue a posse ao usuário deploy:
sudo mkdir -p /var/www/melhorvps-api
sudo chown -R deploy:deploy /var/www/melhorvps-api
cd /var/www/melhorvps-api
Verifique o diretório atual:
pwd
ls -la
Saída esperada:
/var/www/melhorvps-api
total 8
drwxr-xr-x 2 deploy deploy 4096 Jul 13 10:00 .
drwxr-xr-x 3 root root 4096 Jul 13 10:00 ..
Inicialize o projeto e instale o Express:
npm init -y
npm install express
Você deve ver algo parecido com:
Wrote to /var/www/melhorvps-api/package.json
added 68 packages, and audited 69 packages in 3s
found 0 vulnerabilities
Crie o arquivo server.js. Este comando sobrescreve server.js se ele já existir, então use apenas no projeto de teste ou depois de fazer backup.
cat > server.js <<'EOF'
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/health', (req, res) => {
res.json({ status: 'ok', service: 'melhorvps-api' });
});
app.get('/', (req, res) => {
res.send('Aplicação Node.js rodando com PM2');
});
app.listen(port, '127.0.0.1', () => {
console.log(`Servidor ouvindo em http://127.0.0.1:${port}`);
});
EOF
Configure o script de start:
npm pkg set scripts.start='node server.js'
cat package.json
A seção de scripts deve conter:
"scripts": {
"start": "node server.js"
}
Teste a aplicação manualmente antes de envolver PM2:
PORT=3000 npm start
Você verá:
Servidor ouvindo em http://127.0.0.1:3000
Abra outro terminal SSH e teste:
curl http://127.0.0.1:3000/health
Saída esperada:
{"status":"ok","service":"melhorvps-api"}
Volte ao terminal que está rodando o Node e pressione Ctrl+C. Agora a aplicação está validada localmente e pronta para ser gerenciada pelo PM2.
Passo 4: Configurar PM2 para gerenciar a aplicação
O PM2 vai iniciar a aplicação, monitorar o processo e permitir reinício automático. Comece criando um arquivo ecosystem.config.js, que deixa o deploy repetível e evita comandos longos digitados manualmente.
cd /var/www/melhorvps-api
cat > ecosystem.config.js <<'EOF'
module.exports = {
apps: [
{
name: 'melhorvps-api',
script: 'server.js',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production',
PORT: 3000
}
}
]
};
EOF
Inicie a aplicação com PM2:
pm2 start ecosystem.config.js
A saída esperada mostra o processo online:
[PM2] App [melhorvps-api] launched (1 instances)
┌────┬─────────────────┬─────────┬─────────┬────────┬──────┬────────┐
│ id │ name │ mode │ status │ cpu │ mem │ user │
├────┼─────────────────┼─────────┼─────────┼────────┼──────┼────────┤
│ 0 │ melhorvps-api │ fork │ online │ 0% │ 45mb │ deploy │
└────┴─────────────────┴─────────┴─────────┴────────┴──────┴────────┘
Valide o processo e a rota de saúde:
pm2 status
curl http://127.0.0.1:3000/health
Output esperado:
status: online
{"status":"ok","service":"melhorvps-api"}
Agora habilite o PM2 no boot do sistema. Este comando cria uma unidade systemd para o usuário deploy:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy
Você deve ver:
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
Se o PM2 informar um comando adicional, execute exatamente como mostrado. Depois, salve a lista de processos:
pm2 save
Saída esperada:
[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2
Confira a unidade systemd:
systemctl status pm2-deploy --no-pager
Procure por:
Active: active (running)
Neste ponto, se a VPS reiniciar, o PM2 deve restaurar a aplicação automaticamente.
Passo 5: Configurar Nginx como proxy reverso
A aplicação está rodando apenas em 127.0.0.1:3000, o que é bom para segurança. Agora o Nginx vai receber requisições na porta 80 e encaminhar para essa porta interna. Antes de alterar sites do Nginx, faça backup da configuração padrão.
sudo cp -a /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak.$(date +%F-%H%M%S)
Crie uma configuração dedicada:
sudo tee /etc/nginx/sites-available/melhorvps-api > /dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
access_log /var/log/nginx/melhorvps-api.access.log;
error_log /var/log/nginx/melhorvps-api.error.log;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
}
EOF
Desative o site padrão e ative o novo. O próximo comando remove apenas o link simbólico em sites-enabled, não apaga o backup nem o arquivo original em sites-available.
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/melhorvps-api /etc/nginx/sites-enabled/melhorvps-api
Teste a sintaxe antes de recarregar o serviço:
sudo nginx -t
Você precisa ver:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Se o teste falhar, não recarregue. Leia a linha indicada pelo Nginx e corrija o arquivo. Com a sintaxe válida, recarregue:
sudo systemctl reload nginx
Valide localmente pela porta 80:
curl -i http://127.0.0.1/health
A saída esperada inclui status HTTP 200:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"status":"ok","service":"melhorvps-api"}
Se você usa firewall UFW, libere SSH e Nginx antes de ativar. Nunca ative firewall remoto sem garantir a regra de SSH.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo ufw status
Saída esperada:
Status: active
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
Passo 6: Criar um fluxo seguro de atualização
Deploy não termina quando a aplicação sobe. Você precisa de um fluxo previsível para atualizar código, instalar dependências e recarregar o processo sem derrubar tudo por engano. Se seu projeto vem de um repositório Git, o fluxo típico começa no diretório da aplicação.
cd /var/www/melhorvps-api
git status || true
No projeto de teste criado manualmente, talvez você veja:
fatal: not a git repository (or any of the parent directories): .git
Em uma aplicação real clonada por Git, espere algo como:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
Antes de atualizar uma aplicação real, faça backup do diretório atual. Isso consome espaço, mas ajuda a voltar rapidamente em VPS pequenas.
sudo cp -a /var/www/melhorvps-api /var/www/melhorvps-api.release.$(date +%F-%H%M%S)
Se você usa Git, puxe as mudanças e instale dependências de produção:
git pull --ff-only
npm ci --omit=dev
Saídas esperadas:
Already up to date.
added 68 packages, and audited 69 packages in 2s
found 0 vulnerabilities
Se o projeto não tem package-lock.json, npm ci falha. Nesse caso, use npm install --omit=dev, mas considere versionar o lockfile para builds mais previsíveis.
Recarregue a aplicação com PM2:
pm2 reload ecosystem.config.js --update-env
pm2 status
Output esperado:
[PM2] Applying action reloadProcessId on app [melhorvps-api]
status: online
Acompanhe os logs por alguns segundos:
pm2 logs melhorvps-api --lines 30
Você deve ver linhas do Node, por exemplo:
0|melhorvps-api | Servidor ouvindo em http://127.0.0.1:3000
Para sair dos logs, pressione Ctrl+C. Em produção, documente esse fluxo em um arquivo DEPLOY.md no seu repositório. Isso evita deploys improvisados e facilita trabalho em equipe.
Verificação e testes
Agora valide o caminho completo: PM2, aplicação, Nginx e acesso externo. Primeiro, confirme que o processo está online:
pm2 describe melhorvps-api
Procure por campos como:
status online
script path /var/www/melhorvps-api/server.js
node env production
Teste a porta interna:
curl -s http://127.0.0.1:3000/health
Saída esperada:
{"status":"ok","service":"melhorvps-api"}
Teste pelo Nginx local:
curl -s http://127.0.0.1/health
Saída esperada igual:
{"status":"ok","service":"melhorvps-api"}
Teste de fora da VPS, no seu computador:
curl -i http://203.0.113.10/health
Você deve receber:
HTTP/1.1 200 OK
{"status":"ok","service":"melhorvps-api"}
Confira se o Nginx está escutando na porta 80 e o Node apenas no localhost:
sudo ss -tulpn | grep -E ':80|:3000'
Saída esperada:
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx"))
tcp LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node"))
Por fim, simule um reinício controlado do processo, não da VPS inteira:
pm2 restart melhorvps-api
curl -s http://127.0.0.1/health
Se a API responder, o deploy básico está consistente.
Troubleshooting
1. PM2 mostra status erro ou stopped
Veja os logs da aplicação:
pm2 logs melhorvps-api --lines 80
Se aparecer Cannot find module, rode npm install ou npm ci --omit=dev no diretório correto. Se aparecer erro de sintaxe, corrija o código e recarregue:
cd /var/www/melhorvps-api
node server.js
pm2 reload ecosystem.config.js --update-env
Teste com node server.js antes de culpar o PM2. Se falhar fora do PM2, o problema está na aplicação ou nas dependências.
2. Nginx retorna 502 Bad Gateway
Esse erro quase sempre indica que o Nginx não conseguiu conectar em 127.0.0.1:3000. Verifique se a aplicação está online e se a porta está aberta localmente:
pm2 status
sudo ss -tulpn | grep 3000
curl -i http://127.0.0.1:3000/health
Se o curl local falhar, reinicie o processo:
pm2 restart melhorvps-api
Se a aplicação estiver em outra porta, ajuste PORT no ecosystem.config.js ou altere o proxy_pass do Nginx. Depois rode sudo nginx -t e sudo systemctl reload nginx.
3. Acesso externo não abre, mas local funciona
Quando curl http://127.0.0.1/health funciona na VPS, mas o IP público não responde, olhe firewall e regras do provedor. No servidor, verifique UFW:
sudo ufw status
sudo systemctl status nginx --no-pager
Você precisa ver Nginx Full ALLOW. Se não aparecer, execute:
sudo ufw allow 'Nginx Full'
Também confira se o painel do provedor libera portas 80 e 443. Alguns ambientes têm firewall externo além do firewall da VPS.
4. PM2 não volta após reboot
Confirme se a lista de processos foi salva e se o serviço do usuário existe:
pm2 save
systemctl status pm2-deploy --no-pager
ls -la /home/deploy/.pm2/dump.pm2
Se o serviço não existir, rode novamente:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy
pm2 save
Reinicie apenas se puder causar alguns segundos de indisponibilidade. Antes disso, valide pm2 resurrect manualmente.
Próximos passos
Com o deploy funcionando, o próximo passo natural é adicionar HTTPS com Let’s Encrypt e Certbot. Em produção, não deixe APIs públicas apenas em HTTP. Depois, configure variáveis de ambiente com cuidado, de preferência fora do código versionado. Você pode usar um arquivo .env com permissões restritas ou variáveis definidas no ambiente do PM2.
Também vale criar uma rotina de backup para código, uploads e banco de dados. Se a aplicação usa PostgreSQL, MySQL ou Redis, o processo Node.js é apenas uma parte da disponibilidade. Sem backup testado, um deploy bem configurado ainda pode virar dor de cabeça.
Monitore CPU, memória e disco. O PM2 tem pm2 monit, mas ele não substitui ferramentas externas de observabilidade. Para APIs com tráfego real, acompanhe latência, taxa de erro e uso de recursos da VPS. Se você pretende hospedar múltiplas APIs, revise limites de memória, número de processos e estratégia de logs. O material sobre infraestrutura para APIs em VPS aprofunda esse planejamento.
Por fim, transforme os comandos deste guia em um checklist do seu projeto. Um deploy previsível é aquele que outra pessoa consegue repetir sem adivinhar portas, diretórios ou comandos de reinício.
Perguntas frequentes
PM2 é suficiente para colocar uma aplicação Node.js em produção?
PM2 resolve gerenciamento do processo, reinício automático, logs e restauração após reboot, mas não cobre tudo que produção exige. Você ainda precisa configurar Nginx ou outro proxy, HTTPS, firewall, backups, monitoramento e uma rotina segura de deploy. Para aplicações pequenas e médias em VPS, PM2 é uma solução prática e comum. Em ambientes com muitos serviços, containers ou múltiplos servidores, talvez faça sentido usar Docker, systemd diretamente, Kubernetes ou uma plataforma de deploy.
Devo usar fork ou cluster no PM2?
Use fork quando a aplicação é simples, tem pouco tráfego ou quando você quer previsibilidade no primeiro deploy. O modo cluster cria múltiplos processos Node.js e pode aproveitar melhor vários núcleos de CPU, mas exige atenção com sessões em memória, WebSockets e jobs agendados. Se a aplicação usa estado local, comece com fork. Depois, teste cluster em staging com `instances: max` ou um número fixo, verificando logs, consumo de memória e comportamento das rotas críticas.
Por que colocar Nginx na frente do Node.js?
O Nginx recebe conexões HTTP na porta pública, encaminha para o Node.js em uma porta local e facilita recursos como HTTPS, logs de acesso, compressão, limites de tamanho de requisição e roteamento por domínio. Isso evita expor diretamente a porta da aplicação. Também simplifica hospedar várias aplicações na mesma VPS, cada uma rodando em uma porta interna diferente. O Node continua focado na lógica da aplicação, enquanto o Nginx cuida da camada de entrada.
Como atualizo variáveis de ambiente no PM2?
Edite o bloco `env` no `ecosystem.config.js` ou configure variáveis no ambiente do usuário antes de iniciar o processo. Depois, recarregue com `pm2 reload ecosystem.config.js --update-env`. O parâmetro `--update-env` força o PM2 a aplicar as variáveis novas, algo que um restart simples pode não fazer em alguns cenários. Nunca grave segredos diretamente em repositórios públicos. Use permissões restritas, arquivos fora do versionamento ou um gerenciador de segredos quando o projeto exigir mais controle.
Como vejo logs antigos da aplicação no PM2?
Use `pm2 logs nome-do-processo --lines 100` para ver as últimas linhas no terminal. Os arquivos ficam normalmente em `/home/deploy/.pm2/logs/`, separados entre saída padrão e erro. Você pode listar com `ls -lh /home/deploy/.pm2/logs/`. Em produção, configure rotação de logs com `pm2 install pm2-logrotate`, porque logs sem rotação podem ocupar o disco inteiro. Também monitore `/var/log/nginx/`, já que erros de proxy aparecem nos logs do Nginx, não apenas nos logs do Node.
Posso usar este deploy com uma aplicação que usa banco de dados?
Sim, o fluxo com PM2 e Nginx continua válido. A diferença está nas variáveis de conexão, segurança de rede e backups do banco. Configure strings de conexão como variáveis de ambiente, teste a conectividade antes de iniciar o PM2 e não exponha portas de banco publicamente sem necessidade. Se o banco roda na mesma VPS, acompanhe consumo de memória, porque Node.js e banco competem pelos mesmos recursos. Antes de cada deploy, valide migrations em staging ou faça backup do banco.
Fontes consultadas
- PM2 Documentation · coletado em 13/07/2026
- NodeSource Node.js Binary Distributions · coletado em 13/07/2026
- Nginx Admin Guide · coletado em 13/07/2026
- Ubuntu Server documentation · coletado em 13/07/2026