Nginx reverse proxy Node.js-ile ja systemd — juhend

Käivita serveris ühe käsuga (kontrolli enne skripti sisu allpool):

curl -sSL https://cp.lv/scripts/nginx-reverse-proxy-nodejs.sh | sudo bash

…või wgetiga:

wget -qO- https://cp.lv/scripts/nginx-reverse-proxy-nodejs.sh | sudo bash

Skripti sisu nginx-reverse-proxy-nodejs.sh:

#!/usr/bin/env bash
# Nginx reverse proxy and a systemd unit for a Node.js app (myapp)
set -euo pipefail

APP_NAME=myapp
APP_DIR=/var/www/myapp
APP_PORT=3000
NODE_BIN=$(command -v node)

# 1. systemd unit (variables are expanded)
sudo tee /etc/systemd/system/${APP_NAME}.service > /dev/null << EOF
[Unit]
Description=Node.js ${APP_NAME}
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=${APP_DIR}
ExecStart=${NODE_BIN} app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=${APP_PORT}

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now ${APP_NAME}

# 2. Nginx reverse proxy (quoted 'EOF' leaves Nginx variables alone)
sudo tee /etc/nginx/sites-available/${APP_NAME} > /dev/null << 'EOF'
server {
    listen 80;
    server_name app.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_read_timeout 60s;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/${APP_NAME} /etc/nginx/sites-enabled/${APP_NAME}
sudo nginx -t && sudo systemctl reload nginx
echo "Done: ${APP_NAME} is proxied by Nginx to port ${APP_PORT}."

systemd-uniti generaator

Täida väljad — saad valmis .service faili teenuse automaatseks käivitamiseks.

Fail myapp.service — salvesta see kausta /etc/systemd/system/:

Paigaldus ühe käsuga:

Node.js-rakendus kuulab tavaliselt kõrget porti nagu 3000, kuid seda ei tasu väljapoole avada. Õige skeem on Nginx rakenduse ees reverse proxy’na: see võtab vastu päringud portidel 80/443, annab staatika ise välja, tegeleb HTTPS-iga ja proksib dünaamilised päringud Node.js-i. Allpool on Nginxi konfiguratsioon ja systemd-unit rakenduse automaatseks käivitamiseks.

Miks on Node.js-i ette vaja reverse proxy’t

  • HTTPS lõpetatakse Nginxis, rakendus ei pea sertifikaatidega tegelema.
  • Pordid 80/443 on standardsed, Node.js jääb aadressile localhost:3000.
  • Staatika antakse Nginxist otse välja — kiiremini ja väiksema protsessorikuluga.
  • Mitu rakendust elavad ühe IP taga erinevatel domeenidel.

Samm 1. Rakendus kuulab ainult localhosti

Veendu, et Node.js seob end aadressiga 127.0.0.1, mitte 0.0.0.0:

app.listen(3000, '127.0.0.1', () => {
  console.log('server on 127.0.0.1:3000');
});

Siis pole port väljastpoolt otse kättesaadav — ainult Nginxi kaudu.

Samm 2. systemd-unit rakendusele

Käsitsi käivitamise asemel vormista rakendus systemd-teenuseks. Ava fail suvalises konsooliredaktoris (nano, vim või mcedit) ja kleebi sisu:

sudo nano /etc/systemd/system/myapp.service

Faili sisu:

[Unit]
Description=Node.js myapp
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/home/deploy/.nvm/versions/node/v20.11.0/bin/node app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Või loo fail tervikuna ühe käsuga — kopeeri ja kleebi konsooli (heredoc kirjutab kogu faili korraga):

sudo tee /etc/systemd/system/myapp.service > /dev/null << 'EOF'
[Unit]
Description=Node.js myapp
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/home/deploy/.nvm/versions/node/v20.11.0/bin/node app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
EOF

Märgi node binaarfaili täielik tee (leia see käsuga which node). Luba ja käivita teenus:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Rakenduse logid on nüüd journaldis:

sudo journalctl -u myapp -f

Samm 3. Nginxi reverse proxy konfiguratsioon

Ava konfiguratsioonifail redaktoris (sudo nano /etc/nginx/sites-available/myapp) ja kleebi sisu:

server {
    listen 80;
    server_name app.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;

        # WebSocketi tugi
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 60s;
    }
}

Või loo konfiguratsioon tervikuna ühe käsuga (jutumärgid 'EOF' on olulised — Nginxi muutujad $host jäävad nii, nagu nad on):

sudo tee /etc/nginx/sites-available/myapp > /dev/null << 'EOF'
server {
    listen 80;
    server_name app.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_read_timeout 60s;
    }
}
EOF

Päised X-Forwarded-* annavad rakendusele edasi kliendi tegeliku IP ja protokolli. Plokk Upgrade/Connection on vajalik WebSocketi jaoks (socket.io jms).

Samm 4. Aktiveerimine ja HTTPS

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Lisa Let’s Encrypti sertifikaat — certbot kirjutab ise sisse pordi 443 ja ümbersuunamise:

sudo certbot --nginx -d app.example.com

Samm 5. Staatika väljastamine Nginxi kaudu

Et staatikat mitte Node.js-i kaudu ajada, anna see otse välja. Lisa server block’i:

location /static/ {
    alias /var/www/myapp/public/;
    expires 30d;
    access_log off;
}

Diagnostika

Sümptom Põhjus Kontroll
502 Bad Gateway rakendus ei vasta systemctl status myapp
504 Gateway Timeout Node vastab aeglaselt suurenda proxy_read_timeout
WebSocket katkeb puuduvad Upgrade-päised lisa plokk Upgrade/Connection
Klient näeb serveri IP-d puudub X-Real-IP kontrolli proxy_set_header

Korduma kippuvad küsimused

Miks tagastab Nginx 502, kui rakendus töötab? Nginx ei saa proxy_pass-i sihtkohani. Kontrolli, et rakendus kuulab just aadressi 127.0.0.1:3000 ja teenus töötab: journalctl -u myapp.

Kas pm2 on vajalik, kui on olemas systemd? Ei, need on vahetatavad viisid protsessi elus hoidmiseks. systemd on süsteemne ega vaja lisapakette; pm2 on mugavam klastri ja sisseehitatud monitooringu jaoks. Vali üks.

Kuidas anda rakendusele edasi kliendi tegelik IP? Päise X-Real-IP või X-Forwarded-For kaudu. Rakenduses lülita sisse proksi usaldamine (Expressis app.set('trust proxy', 1)).

Kuidas proksida WebSocketit? Lisa plokki location rida proxy_http_version 1.1 ja päised Upgrade/Connection "upgrade", muidu ühendus katkeb.

Nginx + Node.js systemd all on usaldusväärne alus toodangule. Selle jaoks sobiva serveri leiad lehelt VPS-i rent, konteinerpõhise juurutuse jaoks kulub ära Dockeri VPS.

Kristaps Bērziņš