Proxy reverso PM2 + Nginx: conecte um app Node.js à porta 80
Em um servidor Ubuntu 24.04, montamos um proxy reverso Node.js com PM2 e Nginx: o PM2 cuida da execução do app e o Nginx repassa ao app as requisições que chegam na porta 80. Também confirmamos que o PM2 volta a iniciar o app mesmo quando o processo do app é encerrado à força ou o servidor é reiniciado.
As caixas de resultado abaixo mostram a saída de comandos executados com o usuário comum deploy. No lugar do domínio está o valor de exemplo example.com, e as requisições foram enviadas de dentro do servidor com o cabeçalho Host.
1️⃣ Instalar o Node.js
O Node.js é instalado adicionando o repositório da NodeSource ao apt, e escolhemos o Node.js 26, a versão LTS em novembro de 2026.
🔲 NodeSource setup_26.x
export DEBIAN_FRONTEND=noninteractiveSe você definir esse valor antes, não aparece a janela que pede configurações durante a instalação de pacotes. A opção -E do comando sudo abaixo repassa esse valor também ao comando executado com permissão de root.
curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -2026-11-07 16:08:16 - Repository configured successfully.
2026-11-07 16:08:16 - To install Node.js, run: apt install nodejs -yQuando o script termina de adicionar o repositório, você pode instalar o Node.js com o apt como qualquer pacote do Ubuntu.
🔲 apt-get install nodejs
sudo -E apt-get install -y nodejsSetting up libatomic1:amd64 (14.2.0-4ubuntu2~24.04.1) ...
Setting up nodejs (26.13.0-1nodesource1) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Processing triggers for man-db (2.12.0-4build2) ...🔲 node -v, npm -v
node -vv26.13.0npm -v11.20.0O npm já vem no pacote nodejs, então você não precisa instalá-lo à parte.
2️⃣ App de teste em Node.js
Para ver se o PM2 e o Nginx estão bem conectados, crie um app de teste que devolve uma linha de texto ao receber uma requisição.
🔲 app.js
mkdir -p ~/helloCrie a pasta hello dentro da sua pasta pessoal e escreva nela o arquivo app.js.
cat > ~/hello/app.js <<'APP'
const http = require('http');
http.createServer((req, res) => {
console.log(req.method, req.url);
res.end('hello from node\n');
}).listen(3000, '127.0.0.1');
APPO app só aceita requisições na porta 3000 de 127.0.0.1, então não pode ser aberto diretamente de fora do servidor. Vamos configurar o Nginx para receber as requisições de fora e repassá-las a essa porta.
3️⃣ Executar o app com o PM2 e início automático
O PM2 é um gerenciador de processos que executa apps Node.js em segundo plano e volta a iniciar o app quando o processo dele para. Nesta seção também configuramos o início automático, que sobe o app de novo quando o servidor inicia.
🔲 npm install -g pm2
sudo npm install -g pm2added 45 packages in 5spm2 -v[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2Ao executar um comando pm2 pela primeira vez, o daemon do PM2 também é iniciado, e a versão do PM2 instalada é a 7.1.2.
🔲 pm2 start
cd ~/helloVá para a pasta onde está o app.js e execute o app com o PM2.
pm2 start app.js --name hello[PM2] Starting /home/deploy/hello/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ hello │ default │ N/A │ fork │ 17257 │ 0s │ 0 │ online │ 0% │ 26.3mb │ deploy │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘--name é o nome do app que aparece na lista do PM2, e os comandos seguintes indicam o app por esse nome.
pm2 list┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ hello │ default │ N/A │ fork │ 17257 │ 2s │ 0 │ online │ 0% │ 58.5mb │ deploy │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/sonline na coluna status significa que o app está em execução, e a coluna ↺ é o número de vezes que o PM2 reiniciou o app.
🔲 curl 127.0.0.1:3000
Ao enviar uma requisição de dentro do servidor, a linha de texto devolvida pelo app aparece do jeito que veio.
curl -sS http://127.0.0.1:3000/hello from node🔲 pm2 restart
Depois de alterar o código do app, reinicie o app para aplicar a mudança.
pm2 restart helloUse --update-env to update environment variables
[PM2] Applying action restartProcessId on app [hello](ids: [ 0 ])
[PM2] [hello](0) ✓
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ hello │ default │ N/A │ fork │ 17306 │ 0s │ 1 │ online │ 0% │ 19.3mb │ deploy │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/spm2 list┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ hello │ default │ N/A │ fork │ 17306 │ 2s │ 1 │ online │ 0% │ 58.3mb │ deploy │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/sDepois do reinício, o pid mudou de 17257 para 17306 e a coluna ↺ subiu para 1.
🔲 kill -9
Você pode simular uma parada repentina do processo do app com kill -9, e pm2 pid mostra o número do processo do app.
kill -9 "$(pm2 pid hello)"Confira a lista do PM2 de novo 3 segundos depois.
pm2 list┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ hello │ default │ N/A │ fork │ 17797 │ 3s │ 2 │ online │ 0% │ 58.6mb │ deploy │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 51.2% | ram usage: 35.8% | eth0: ⇓ 1kb/s ⇑ 1kb/s | eth1: ⇓ 19kb/s ⇑ 0mb/s | disk: ⇓ 728kb/s ⇑ 3.1mb/sO PM2 tinha iniciado o app de novo com o novo pid 17797 e ele estava online, e a coluna ↺ tinha subido para 2.
🔲 pm2 save
pm2 save[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2O pm2 save grava a lista de apps em execução no arquivo dump.pm2, e no boot o PM2 volta a iniciar os apps conforme essa lista.
🔲 pm2 startup
Para o PM2 rodar no boot, registre o PM2 como serviço do systemd. O comando abaixo cria o serviço com o nome da conta atual e a pasta pessoal dela.
sudo env PATH="$PATH" pm2 startup systemd -u "$(id -un)" --hp "$HOME"ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill
[Install]
WantedBy=multi-user.target
Target path
/etc/systemd/system/pm2-deploy.service
Command list
[ 'systemctl enable pm2-deploy' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-deploy.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-deploy...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-deploy.service → /etc/systemd/system/pm2-deploy.service.
[PM2] [v] Command successfully executed.env PATH é a parte que permite que o pm2, executado com permissão de root, encontre o caminho do node da conta atual, e -u e --hp são a conta e a pasta pessoal com que o serviço roda. A linha ExecStart do arquivo de serviço é pm2 resurrect, que restaura no boot a lista de apps salva.
systemctl status pm2-deploy --no-pager | head -n 5○ pm2-deploy.service - PM2 process manager
Loaded: loaded (/etc/systemd/system/pm2-deploy.service; enabled; preset: enabled)
Active: inactive (dead)
Docs: https://pm2.keymetrics.io/enabled significa que o serviço roda no boot. O pm2 startup só registra o serviço e não o inicia agora, por isso aparece inactive (dead), e o serviço roda a partir do próximo boot.
🔲 pm2 list após reiniciar
sudo systemctl rebootCerca de 2 minutos depois, conecte de novo como deploy e confira o estado do app.
uptime -pup 2 minutespm2 list┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ hello │ default │ N/A │ fork │ 976 │ 108s │ 0 │ online │ 0% │ 63.0mb │ deploy │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0.3% | ram usage: 35.1% | disk: ⇓ 4kb/s ⇑ 4kb/sDois minutos depois de o servidor ligar, o app já estava online havia 108 segundos: o PM2 tinha iniciado o app de novo junto com o boot.
4️⃣ Configurar o proxy reverso do Nginx
Como o app só aceita requisições em 127.0.0.1:3000, o Nginx recebe as requisições que chegam na porta 80 e as repassa ao app. Essa configuração, em que um servidor na frente recebe as requisições e as encaminha para um servidor atrás, se chama proxy reverso.
🔲 apt-get install nginx
sudo -E apt-get install -y nginxSetting up nginx-common (1.24.0-2ubuntu7.19) ...
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
Setting up nginx (1.24.0-2ubuntu7.19) ...nginx -vnginx version: nginx/1.24.0 (Ubuntu)O Nginx do repositório do Ubuntu 24.04 é a versão 1.24.0, e a instalação já o registra para rodar no boot.
🔲 /etc/nginx/sites-available/hello
Crie o arquivo /etc/nginx/sites-available/hello e coloque nele o conteúdo abaixo.
server {
listen 80;
server_name example.com;
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_pass é o endereço do app para onde as requisições são repassadas. Os cabeçalhos Host, X-Real-IP, X-Forwarded-For e X-Forwarded-Proto são valores repassados para que o app saiba o domínio da requisição original, o IP do visitante e se ela foi http ou https.
proxy_http_version 1.1 e os cabeçalhos Upgrade e Connection são necessários para levar conexões WebSocket até o app. Os cabeçalhos Upgrade e Connection não chegam ao app depois de passar por um proxy, por isso são colocados direto na configuração.
sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/helloO Nginx só lê as configurações que têm um link na pasta sites-enabled.
🔲 nginx -t
sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsudo systemctl reload nginxVerifique primeiro a sintaxe da configuração com nginx -t e, se passar, aplique a nova configuração com reload sem parar o Nginx.
🔲 Requisição curl com cabeçalho Host
curl -sS -i -H 'Host: example.com' http://127.0.0.1/ | head -n 4HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16Depois de recarregar a configuração, a requisição enviada com o cabeçalho Host recebeu a resposta do app. A linha Server dos cabeçalhos de resposta é nginx, e Content-Length 16 tem o mesmo tamanho da linha hello from node devolvida pelo app (16 bytes contando a quebra de linha). Para conectar de fora do servidor, a porta 80 precisa estar aberta no firewall.
5️⃣ Aplicar HTTPS com o Certbot
Com o plugin Nginx do Certbot, você emite o certificado e altera a configuração do Nginx de uma vez. Execute o comando abaixo quando o domínio já estiver apontando para o IP do servidor.
sudo certbot --nginxA instalação do Certbot e o teste de renovação automática estão no artigo abaixo; o comando de emissão daquele artigo é para Apache, então troque só a etapa de emissão pelo comando acima.
🔢 FAQ & conteúdo recomendado
Logs de serviços
Comando journalctl: uso, logs por serviço e limpeza do tamanho do journal
Artigo sobre como ler, serviço por serviço, os logs de serviços do systemd como nginx e pm2-deploy.
ℹ️ Aviso de afiliados
O conteúdo deste site contém links de afiliados. Quando um visitante compra um produto ou serviço por meio de um desses links, o site recebe uma comissão do vendedor. O valor que o comprador paga (em promoções, ele diminui ↓) não aumenta por causa disso. Preços, descontos e informações de estoque valem para o momento em que o texto foi escrito e podem ter mudado, por isso confirme com o vendedor antes de comprar. Os produtos são escolhidos e avaliados por critérios próprios, e o recebimento de comissão não influencia a ordem de apresentação nem o conteúdo das avaliações.