Reverse proxy PM2 + Nginx: podłącz aplikację Node.js do portu 80

Środowisko testoweRodzina DebianUbuntu 24.04.5 LTSzwykłe konto użytkownika
Działanie aplikacji Node.js i reverse proxy sprawdzone

Na serwerze Ubuntu 24.04 skonfigurowaliśmy reverse proxy Node.js z PM2 i Nginx: PM2 odpowiada za uruchamianie aplikacji, a Nginx przekazuje do aplikacji żądania przychodzące na port 80. Sprawdziliśmy też, że PM2 ponownie uruchamia aplikację nawet po wymuszonym zakończeniu jej procesu lub restarcie serwera.

Poniższe pola z wynikami pokazują dane wyjściowe poleceń uruchomionych jako zwykły użytkownik deploy. W miejscu domeny jest przykładowa wartość example.com, a żądania wysłano z wnętrza serwera z nagłówkiem Host.

🎯 Najważniejsze
🗂️ Przepływ żądania
📂 NginxReverse proxy
📄 CertbotCertyfikat HTTPS
📂 PM2Uruchamianie i autostart
📄 Aplikacja Node.js
Uruchamianie aplikacji przez PM2 i start przy rozruchu
Nginx przekazuje żądania z portu 80 do aplikacji
Wymagania
SerwerUbuntu 24.04 po konfiguracji początkowejsudo włączone dla zwykłego konta użytkownikaDomena potrzebna do HTTPS

1️⃣ Instalacja Node.js

Node.js instaluje się, dodając repozytorium NodeSource do apt, a jako wersję wybraliśmy Node.js 26, czyli wersję LTS na listopad 2026.

🔲 NodeSource setup_26.x

export DEBIAN_FRONTEND=noninteractive

Jeśli najpierw ustawisz tę wartość, podczas instalacji pakietów nie pojawi się okno z pytaniem o ustawienia. Opcja -E polecenia sudo poniżej przekazuje tę wartość także do polecenia uruchamianego z uprawnieniami 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

Gdy skrypt skończy dodawać repozytorium, możesz zainstalować Node.js przez apt jak każdy pakiet 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

npm jest w pakiecie nodejs, więc nie musisz instalować go osobno.

2️⃣ Testowa aplikacja Node.js

Aby sprawdzić, czy PM2 i Nginx są dobrze połączone, utwórz testową aplikację, która po otrzymaniu żądania zwraca jedną linię tekstu.

🔲 app.js

mkdir -p ~/hello

Utwórz folder hello w swoim folderze domowym i zapisz w nim plik 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

Aplikacja przyjmuje żądania tylko na porcie 3000 adresu 127.0.0.1, więc nie da się jej otworzyć bezpośrednio spoza serwera. Nginx skonfigurujemy tak, aby odbierał żądania z zewnątrz i przekazywał je na ten port.

3️⃣ Uruchamianie aplikacji przez PM2 i autostart

PM2 to menedżer procesów, który uruchamia aplikacje Node.js w tle i ponownie uruchamia aplikację, gdy jej proces się zatrzyma. W tej części konfigurujemy też autostart, który ponownie uruchamia aplikację przy rozruchu serwera.

🔲 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

Przy pierwszym poleceniu pm2 uruchamia się też demon PM2, a zainstalowane PM2 ma wersję 7.1.2.

🔲 pm2 start

cd ~/hello

Przejdź do folderu z plikiem app.js, a potem uruchom aplikację przez 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 to nazwa aplikacji widoczna na liście PM2, a kolejne polecenia wskazują aplikację tą nazwą.

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 w kolumnie status oznacza, że aplikacja działa, a kolumna ↺ to liczba ponownych uruchomień aplikacji przez PM2.

🔲 curl 127.0.0.1:3000

Gdy wyślesz żądanie z wnętrza serwera, linia tekstu zwrócona przez aplikację pojawi się bez zmian.

curl -sS http://127.0.0.1:3000/
hello from node

🔲 pm2 restart

Po zmianie kodu aplikacji uruchom ją ponownie, aby zastosować zmianę.

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

Po ponownym uruchomieniu pid zmienił się z 17257 na 17306, a kolumna ↺ wzrosła do 1.

🔲 kill -9

Nagłe zatrzymanie procesu aplikacji można wywołać poleceniem kill -9, a pm2 pid wypisuje numer procesu aplikacji.

kill -9 "$(pm2 pid hello)"

Po 3 sekundach sprawdź ponownie listę PM2.

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

PM2 ponownie uruchomiło aplikację z nowym pid 17797 i była ona online, a kolumna ↺ wzrosła do 2.

🔲 pm2 save

pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2

pm2 save zapisuje listę działających aplikacji w pliku dump.pm2, a przy rozruchu PM2 ponownie uruchamia aplikacje według tej listy.

🔲 pm2 startup

Aby PM2 działało przy rozruchu, zarejestruj PM2 jako usługę systemd. Poniższe polecenie tworzy usługę z nazwą bieżącego konta i jego folderem domowym.

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 to część, dzięki której pm2 uruchamiane z uprawnieniami root znajduje ścieżkę node bieżącego konta, a -u i --hp to konto i folder domowy, z którymi działa usługa. Wiersz ExecStart w pliku usługi to pm2 resurrect, które przy rozruchu przywraca zapisaną listę aplikacji.

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 oznacza, że usługa uruchamia się przy rozruchu. pm2 startup tylko rejestruje usługę i nie uruchamia jej teraz, dlatego widać inactive (dead), a usługa działa od następnego rozruchu.

🔲 pm2 list po restarcie

sudo systemctl reboot

Po około 2 minutach połącz się ponownie jako deploy i sprawdź stan aplikacji.

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

Dwie minuty po włączeniu serwera aplikacja była już online od 108 sekund: PM2 uruchomiło ją ponownie razem z rozruchem.

4️⃣ Konfiguracja reverse proxy w Nginx

Ponieważ aplikacja przyjmuje żądania tylko na 127.0.0.1:3000, Nginx odbiera żądania przychodzące na port 80 i przekazuje je do aplikacji. Taki układ, w którym serwer z przodu odbiera żądania i przekazuje je do serwera z tyłu, nazywa się reverse proxy.

🔲 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)

Nginx z repozytorium Ubuntu 24.04 ma wersję 1.24.0 i przy instalacji od razu rejestruje się do uruchamiania przy rozruchu.

🔲 /etc/nginx/sites-available/hello

Utwórz plik /etc/nginx/sites-available/hello i wpisz do niego poniższą treść.

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 to adres aplikacji, do której przekazywane są żądania. Nagłówki Host, X-Real-IP, X-Forwarded-For i X-Forwarded-Proto to wartości przekazywane po to, aby aplikacja znała domenę pierwotnego żądania, IP odwiedzającego oraz to, czy było to http, czy https.

proxy_http_version 1.1 oraz nagłówki Upgrade i Connection są potrzebne, aby przekazać połączenia WebSocket aż do aplikacji. Nagłówki Upgrade i Connection po przejściu przez proxy nie trafiają do aplikacji, dlatego wpisuje się je bezpośrednio w konfiguracji.

sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/hello

Nginx czyta tylko te konfiguracje, dla których w folderze sites-enabled jest dowiązanie.

🔲 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

Najpierw sprawdź składnię konfiguracji poleceniem nginx -t, a jeśli przejdzie, zastosuj nową konfigurację przez reload bez zatrzymywania Nginx.

🔲 Żądanie curl z nagłówkiem 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

Po ponownym wczytaniu konfiguracji na żądanie z nagłówkiem Host wróciła odpowiedź aplikacji. Wiersz Server w nagłówkach odpowiedzi to nginx, a Content-Length 16 ma ten sam rozmiar co linia hello from node zwracana przez aplikację (16 bajtów ze znakiem nowej linii). Aby łączyć się spoza serwera, port 80 musi być otwarty w zaporze.

🧭 Żądania do aplikacji Node.js i ponowne uruchomienie
Diagram objaśniający
🧭 Sytuacja
Przepływ żądania
🌐PrzeglądarkaŻądanie do example.com
➜
🧱NginxPort 80, proxy_pass
➜
🟢Aplikacja Node.js127.0.0.1:3000
Zarządzanie procesami
⚙️systemdUsługa pm2-deploy
➜
🔁PM2Lista aplikacji dump.pm2
➜
🟢Aplikacja Node.jshello
Nginx na porcie 80 odbiera żądanie przeglądarki, przekazuje je do 127.0.0.1:3000 wpisanego w proxy_pass i zwraca odpowiedź aplikacji bez zmian.
Gdy proces aplikacji się zatrzyma, PM2 od razu ponownie uruchamia tę samą aplikację. Po zatrzymaniu przez kill -9 pm2 list 3 sekundy później pokazało aplikację online z nowym pid.
Przy rozruchu serwera systemd uruchamia usługę pm2-deploy, a PM2 ponownie uruchamia aplikacje według listy zapisanej przez pm2 save.

5️⃣ Włączenie HTTPS przez Certbot

Wtyczka Nginx w Certbot pozwala za jednym razem uzyskać certyfikat i zmienić konfigurację Nginx. Uruchom poniższe polecenie, gdy domena wskazuje już na IP serwera.

sudo certbot --nginx

Instalację Certbot i test automatycznego odnawiania opisuje artykuł poniżej; polecenie wydania certyfikatu z tamtego artykułu jest dla Apache, więc zamień tylko krok wydania na polecenie powyżej.

🔢 FAQ & polecane treści

Gdy Nginx nie może połączyć się z adresem aplikacji wpisanym w proxy_pass, zwraca 502 Bad Gateway. Po zatrzymaniu aplikacji poleceniem pm2 stop hello to samo żądanie zwróciło 502, a w /var/log/nginx/error.log został wiersz [connect() failed (111: Connection refused) while connecting to upstream] z adresem, z którym próbował się połączyć, http://127.0.0.1:3000/. Najpierw sprawdź w pm2 list, czy aplikacja jest online i czy port, na którym aplikacja przyjmuje żądania, zgadza się z adresem w proxy_pass. Po ponownym uruchomieniu aplikacji to samo żądanie wróciło z kodem 200.

PM2 zapisuje dane wyjściowe aplikacji w plikach nazwanych od aplikacji, w folderze .pm2/logs w folderze domowym konta, które ją uruchomiło. Polecenie pm2 logs hello --nostream --lines 5 pokazało ostatnie linie dwóch plików, /home/deploy/.pm2/logs/hello-out.log i hello-error.log, a w pliku out było żądanie GET / zapisane przez aplikację. Bez --nostream polecenie dalej wypisuje nowe logi, gdy się pojawiają.

Usługę autostartu usuwa się poleceniem pm2 unstartup systemd. Po dodaniu lub usunięciu aplikacji uruchom ponownie pm2 save, aby przy rozruchu aplikacje działały według zmienionej listy. Po uruchomieniu pm2 startup te dwa polecenia też były podane na końcu danych wyjściowych.

ℹ️ Informacja o linkach partnerskich
Treści tej witryny zawierają linki partnerskie. Jeśli odwiedzający kupi produkt lub usługę przez taki link, witryna otrzymuje od sprzedawcy prowizję. Kwota płacona przy tym przez kupującego (podczas promocji spada↓) nie wzrasta. Podane ceny, rabaty i dostępność odnoszą się do chwili pisania i mogą różnić się od stanu faktycznego, dlatego przed zakupem sprawdź je u sprzedawcy. Produkty wybieramy i oceniamy według własnych kryteriów, a prowizja nie wpływa ani na kolejność prezentacji, ani na treść ocen.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *

Prove your humanity: 8   +   10   =