Обратный прокси PM2 + Nginx: подключение приложения Node.js к порту 80

Тестовая средаСемейство DebianUbuntu 24.04.5 LTSобычная учётная запись пользователя
Работа приложения Node.js и обратного прокси проверена

На сервере Ubuntu 24.04 мы развернули обратный прокси Node.js с PM2 и Nginx: PM2 отвечает за запуск приложения, а Nginx передаёт приложению запросы, приходящие на порт 80. Мы также убедились, что PM2 снова запускает приложение, даже если процесс приложения принудительно завершён или сервер перезагружен.

Блоки с результатами ниже — это вывод команд, выполненных от обычного пользователя deploy. Вместо домена стоит пример example.com, а запросы отправлялись изнутри сервера с заголовком Host.

🎯 Главное
🗂️ Путь запроса
📂 NginxОбратный прокси
📄 CertbotСертификат HTTPS
📂 PM2Запуск и автозапуск
📄 Приложение Node.js
Запуск приложения через PM2 и автозапуск при загрузке
Nginx передаёт приложению запросы с порта 80
Предварительные условия
СерверUbuntu 24.04 после начальной настройкиsudo включён для обычной учётной записиДомен нужен для HTTPS

1️⃣ Установка Node.js

Node.js устанавливается после добавления репозитория NodeSource в apt; мы выбрали Node.js 26 — версию LTS на ноябрь 2026 года.

🔲 NodeSource setup_26.x

export DEBIAN_FRONTEND=noninteractive

Если сначала задать это значение, во время установки пакетов не появится окно с вопросами о настройках. Опция -E у команды sudo ниже передаёт это значение и команде, которая выполняется с правами 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

Когда скрипт закончит добавлять репозиторий, вы сможете установить Node.js через apt, как любой пакет 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 входит в пакет nodejs, поэтому отдельно его устанавливать не нужно.

2️⃣ Тестовое приложение Node.js

Чтобы проверить, правильно ли связаны PM2 и Nginx, создайте тестовое приложение, которое в ответ на запрос возвращает одну строку текста.

🔲 app.js

mkdir -p ~/hello

Создайте папку hello в домашней папке и запишите в неё файл 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

Приложение принимает запросы только на порту 3000 адреса 127.0.0.1, поэтому напрямую извне сервера его не открыть. Мы настроим Nginx так, чтобы он принимал внешние запросы и передавал их на этот порт.

3️⃣ Запуск приложения через PM2 и автозапуск

PM2 — это менеджер процессов, который запускает приложения Node.js в фоне и снова запускает приложение, когда его процесс завершается. В этом разделе мы также настроим автозапуск, который поднимает приложение при загрузке сервера.

🔲 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

При первом запуске команды pm2 вместе с ней стартует демон PM2; установленная версия PM2 — 7.1.2.

🔲 pm2 start

cd ~/hello

Перейдите в папку с app.js и запустите приложение через 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 — это имя приложения в списке PM2; дальнейшие команды указывают приложение по этому имени.

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 в столбце status означает, что приложение работает, а столбец ↺ показывает, сколько раз PM2 перезапускал приложение.

🔲 curl 127.0.0.1:3000

Если отправить запрос изнутри сервера, строка текста, которую возвращает приложение, придёт без изменений.

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

🔲 pm2 restart

После изменения кода приложения перезапустите его, чтобы изменения вступили в силу.

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

После перезапуска pid сменился с 17257 на 17306, а столбец ↺ вырос до 1.

🔲 kill -9

Внезапную остановку процесса приложения можно смоделировать командой kill -9, а pm2 pid выводит номер процесса приложения.

kill -9 "$(pm2 pid hello)"

Через 3 секунды снова проверьте список 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 снова запустил приложение с новым pid 17797, оно было в состоянии online, а столбец ↺ вырос до 2.

🔲 pm2 save

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

pm2 save сохраняет список запущенных приложений в файл dump.pm2, и при загрузке PM2 снова запускает приложения по этому списку.

🔲 pm2 startup

Чтобы PM2 запускался при загрузке, зарегистрируйте PM2 как службу systemd. Команда ниже создаёт службу с именем текущей учётной записи и её домашней папкой.

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 — это часть, которая позволяет pm2, запущенному с правами root, найти путь к node текущей учётной записи, а -u и --hp — учётная запись и домашняя папка, от которых работает служба. Строка ExecStart в файле службы — это pm2 resurrect, который при загрузке восстанавливает сохранённый список приложений.

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 означает, что служба запускается при загрузке. pm2 startup только регистрирует службу и не запускает её сейчас, поэтому видно inactive (dead), а служба начнёт работать со следующей загрузки.

🔲 pm2 list после перезагрузки

sudo systemctl reboot

Примерно через 2 минуты снова подключитесь как deploy и проверьте состояние приложения.

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

Через 2 минуты после включения сервера приложение уже 108 секунд было online: PM2 снова запустил его вместе с загрузкой.

4️⃣ Настройка обратного прокси Nginx

Так как приложение принимает запросы только на 127.0.0.1:3000, запросы, приходящие на порт 80, принимает Nginx и передаёт приложению. Такая схема, при которой сервер впереди принимает запросы и пересылает их серверу позади, называется обратным прокси.

🔲 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 из репозитория Ubuntu 24.04 имеет версию 1.24.0 и при установке сразу регистрируется для запуска при загрузке.

🔲 /etc/nginx/sites-available/hello

Создайте файл /etc/nginx/sites-available/hello и поместите в него содержимое ниже.

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 — это адрес приложения, которому передаются запросы. Заголовки Host, X-Real-IP, X-Forwarded-For и X-Forwarded-Proto — значения, которые передаются, чтобы приложение знало домен исходного запроса, IP посетителя и был ли запрос по http или https.

proxy_http_version 1.1 и заголовки Upgrade и Connection нужны, чтобы передавать соединения WebSocket до самого приложения. Заголовки Upgrade и Connection не доходят до приложения после прохождения через прокси, поэтому их задают прямо в настройках.

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

Nginx читает только те настройки, для которых в папке 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

Сначала проверьте синтаксис настроек командой nginx -t и, если проверка пройдена, примените новые настройки через reload, не останавливая Nginx.

🔲 Запрос curl с заголовком 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

После перечитывания настроек запрос с заголовком Host получил ответ приложения. Строка Server в заголовках ответа — nginx, а Content-Length 16 совпадает по размеру со строкой hello from node, которую возвращает приложение (16 байт с переводом строки). Чтобы подключаться извне сервера, порт 80 должен быть открыт в брандмауэре.

🧭 Запросы к приложению Node.js и перезапуск
Поясняющая схема
🧭 Ситуация
Путь запроса
🌐БраузерЗапрос к example.com
➜
🧱NginxПорт 80, proxy_pass
➜
🟢Приложение Node.js127.0.0.1:3000
Управление процессами
⚙️systemdСлужба pm2-deploy
➜
🔁PM2Список приложений dump.pm2
➜
🟢Приложение Node.jshello
Запрос браузера принимает Nginx на порту 80, передаёт его на 127.0.0.1:3000, указанный в proxy_pass, и возвращает ответ приложения без изменений.
Когда процесс приложения завершается, PM2 сразу снова запускает то же приложение. После остановки через kill -9 команда pm2 list через 3 секунды показала приложение в состоянии online с новым pid.
При загрузке сервера systemd запускает службу pm2-deploy, а PM2 снова запускает приложения по списку, сохранённому через pm2 save.

5️⃣ Подключение HTTPS через Certbot

С плагином Nginx для Certbot можно за один раз выпустить сертификат и изменить настройки Nginx. Выполните команду ниже, когда домен уже указывает на IP сервера.

sudo certbot --nginx

Установка Certbot и проверка автоматического продления описаны в статье ниже; команда выпуска в той статье рассчитана на Apache, поэтому замените только шаг выпуска на команду выше.

🔢 FAQ & рекомендуемые материалы

Если Nginx не может подключиться к адресу приложения, указанному в proxy_pass, он возвращает 502 Bad Gateway. После остановки приложения командой pm2 stop hello тот же запрос вернул 502, а в /var/log/nginx/error.log осталась строка [connect() failed (111: Connection refused) while connecting to upstream] вместе с адресом, к которому шло подключение, http://127.0.0.1:3000/. Сначала проверьте в pm2 list, что приложение в состоянии online и что порт, на котором приложение принимает запросы, совпадает с адресом в proxy_pass. После перезапуска приложения тот же запрос вернулся с кодом 200.

PM2 сохраняет вывод приложения в файлы по имени приложения в папке .pm2/logs внутри домашней папки учётной записи, от которой оно запущено. Команда pm2 logs hello --nostream --lines 5 показала последние строки двух файлов, /home/deploy/.pm2/logs/hello-out.log и hello-error.log, а в файле out был запрос GET /, записанный приложением. Без --nostream команда продолжает выводить новые записи журнала по мере их появления.

Службу автозапуска удаляют командой pm2 unstartup systemd. После добавления или удаления приложений снова выполните pm2 save, чтобы при загрузке приложения запускались по изменённому списку. При запуске pm2 startup эти две команды тоже были указаны в конце вывода.

ℹ️ О партнёрских ссылках
Материалы этого сайта содержат партнёрские ссылки. Если посетитель покупает товар или услугу по такой ссылке, сайт получает комиссию от продавца. При этом сумма, которую платит покупатель (при акционных скидках сумма снижается ↓), не увеличивается. Указанные цены, скидки и наличие актуальны на момент написания и могут отличаться, поэтому перед покупкой уточняйте их у продавца. Товары выбираются и оцениваются по нашим собственным критериям, а комиссия не влияет ни на порядок, ни на содержание обзоров.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Докажите, что вы человек: 10   +   5   =