Proxy reverso PM2 + Nginx: conecte um app Node.js à porta 80

Ambiente de testeFamília DebianUbuntu 24.04.5 LTSconta de usuário comum
Operação do app Node.js e proxy reverso verificados

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.

🎯 Pontos principais
🗂️ Fluxo da requisição
📂 NginxProxy reverso
📄 CertbotCertificado HTTPS
📂 PM2Execução e início automático
📄 App Node.js
Executar o app com o PM2 e iniciá-lo no boot
Nginx repassa ao app as requisições da porta 80
Pré-requisitos
ServidorUbuntu 24.04 com a configuração inicial concluídasudo liberado para uma conta de usuário comumDomínio necessário para HTTPS

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=noninteractive

Se 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 -y

Quando 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 nodejs
Setting 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 -v
v26.13.0
npm -v
11.20.0

O 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 ~/hello

Crie 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');
APP

O 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 pm2
added 45 packages in 5s
pm2 -v
[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2

Ao 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 ~/hello

Vá 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/s

online 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 hello
Use --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/s
pm2 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/s

Depois 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/s

O 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.pm2

O 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 reboot

Cerca de 2 minutos depois, conecte de novo como deploy e confira o estado do app.

uptime -p
up 2 minutes
pm2 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/s

Dois 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 nginx
Setting 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 -v
nginx 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/hello

O Nginx só lê as configurações que têm um link na pasta sites-enabled.

🔲 nginx -t

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

Verifique 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 4
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16

Depois 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.

🧭 Requisições ao app Node.js e reinício
Diagrama explicativo
🧭 Situação
Fluxo da requisição
🌐NavegadorRequisição para example.com
➜
🧱NginxPorta 80, proxy_pass
➜
🟢App Node.js127.0.0.1:3000
Gerenciamento de processos
⚙️systemdServiço pm2-deploy
➜
🔁PM2Lista de apps dump.pm2
➜
🟢App Node.jshello
O Nginx na porta 80 recebe a requisição do navegador, repassa para 127.0.0.1:3000 definido em proxy_pass e devolve a resposta do app do jeito que veio.
Quando o processo do app para, o PM2 inicia o mesmo app de novo na hora. Depois de parar com kill -9, o pm2 list 3 segundos depois mostrava o app online com um pid novo.
Quando o servidor inicia, o systemd executa o serviço pm2-deploy, e o PM2 volta a iniciar os apps conforme a lista salva com pm2 save.

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 --nginx

A 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

Quando o Nginx não consegue se conectar ao endereço do app definido em proxy_pass, ele retorna 502 Bad Gateway. Depois de parar o app com pm2 stop hello, a mesma requisição retornou 502, e o /var/log/nginx/error.log registrou a linha [connect() failed (111: Connection refused) while connecting to upstream] com o endereço que ele tentou acessar, http://127.0.0.1:3000/. Comece verificando no pm2 list se o app está online e se a porta em que o app recebe requisições é a mesma do endereço em proxy_pass. Depois de reiniciar o app, a mesma requisição voltou com 200.

O PM2 grava a saída do app em arquivos com o nome de cada app na pasta .pm2/logs, dentro da pasta pessoal da conta que o executou. Ao rodar pm2 logs hello --nostream --lines 5, apareceram as últimas linhas de dois arquivos, /home/deploy/.pm2/logs/hello-out.log e hello-error.log, e o arquivo out tinha a requisição GET / registrada pelo app. Sem --nostream, ele continua mostrando os logs novos à medida que chegam.

O serviço de início automático é removido com pm2 unstartup systemd. Depois de adicionar ou remover apps, execute pm2 save de novo para que no boot os apps rodem conforme a lista alterada. Ao executar pm2 startup, esses dois comandos também apareceram no fim da saída.

ℹ️ 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.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Demonstre sua humanidade: 6   +   6   =