Nginx reverse proxy Node.js programai + systemd

Paleiskite serveryje viena komanda (pirmiausia patikrinkite skripto turinį žemiau):

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

…arba su wget:

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

Skripto turinys 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 vieneto generatorius

Užpildykite laukus — gausite paruoštą .service failą serviso automatiniam paleidimui.

Failas myapp.service — išsaugokite jį kataloge /etc/systemd/system/:

Diegimas viena komanda:

Node.js programa paprastai klauso aukšto prievado, pavyzdžiui 3000, tačiau atverti jo į išorę neverta. Teisinga schema — Nginx prieš programą kaip reverse proxy: jis priima užklausas 80/443 prievaduose, atiduoda statiką, terminuoja HTTPS ir proksina dinamiką į Node.js. Žemiau — Nginx konfigūracija ir systemd vienetas automatiniam programos paleidimui.

Kam reikalingas reverse proxy prieš Node.js

  • HTTPS terminuojamas Nginx pusėje, programai nereikia rūpintis sertifikatais.
  • 80/443 prievadai yra standartiniai, o Node.js lieka adresu localhost:3000.
  • Statiką Nginx atiduoda tiesiogiai — greičiau ir pigiau CPU atžvilgiu.
  • Kelios programos gyvena už vieno IP skirtinguose domenuose.

1 žingsnis. Programa klauso tik localhost

Įsitikinkite, kad Node.js prisiriša prie 127.0.0.1, o ne prie 0.0.0.0:

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

Tada iš išorės prievadas tiesiogiai nepasiekiamas — tik per Nginx.

2 žingsnis. systemd vienetas programai

Vietoje rankinio paleidimo aprašykite programą kaip systemd servisą. Atidarykite failą bet kuriuo konsolės redaktoriumi (nano, vim arba mcedit) ir įklijuokite turinį:

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

Failo turinys:

[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

Arba sukurkite visą failą viena komanda — nukopijuokite ir įklijuokite į konsolę (heredoc įrašys visą failą iš karto):

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

Nurodykite pilną kelią iki node dvejetainio failo (raskite jį komanda which node). Įjunkite ir paleiskite servisą:

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

Programos žurnalai dabar yra journald:

sudo journalctl -u myapp -f

3 žingsnis. Nginx reverse proxy konfigūracija

Atidarykite konfigūraciją redaktoriuje (sudo nano /etc/nginx/sites-available/myapp) ir įklijuokite turinį:

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;

        # WebSocket palaikymas
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 60s;
    }
}

Arba sukurkite visą konfigūraciją viena komanda (kabutės 'EOF' svarbios — Nginx kintamieji $host liks nepakeisti):

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

Antraštės X-Forwarded-* perduoda programai tikrą kliento IP ir protokolą. Blokas Upgrade/Connection reikalingas WebSocket (socket.io ir pan.).

4 žingsnis. Aktyvavimas ir HTTPS

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

Pridėkite Let’s Encrypt sertifikatą — certbot pats įrašys 443 prievadą ir peradresavimą:

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

5 žingsnis. Statikos atidavimas per Nginx

Kad statika nekeliautų per Node.js, atiduokite ją tiesiogiai. Pridėkite į server block:

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

Diagnostika

Simptomas Priežastis Patikra
502 Bad Gateway programa neatsako systemctl status myapp
504 Gateway Timeout ilgas Node atsakymas padidinkite proxy_read_timeout
WebSocket nutrūksta nėra Upgrade antraščių pridėkite Upgrade/Connection bloką
Klientas mato serverio IP nėra X-Real-IP patikrinkite proxy_set_header

Dažni klausimai

Kodėl Nginx grąžina 502, nors programa veikia? Nginx nepasiekia proxy_pass adreso. Patikrinkite, ar programa klauso būtent 127.0.0.1:3000 ir ar servisas paleistas: journalctl -u myapp.

Ar reikia pm2, jei yra systemd? Ne, tai tarpusavyje pakeičiami būdai palaikyti procesą gyvą. systemd yra sisteminis ir nereikalauja papildomų paketų; pm2 patogesnis klasteriui ir turi įmontuotą stebėseną. Rinkitės vieną.

Kaip perduoti programai tikrą kliento IP? Per antraštę X-Real-IP arba X-Forwarded-For. Programoje įjunkite pasitikėjimą proxy serveriu (Express atveju — app.set('trust proxy', 1)).

Kaip proksinti WebSocket? Pridėkite proxy_http_version 1.1 ir antraštes Upgrade/Connection "upgrade" į location bloką, kitaip ryšys nutrūks.

Nginx + Node.js schema su systemd yra patikimas gamybinės aplinkos pagrindas. Serverį jai galite išsirinkti puslapyje VPS serverio nuoma, o konteinerių diegimui pravers Docker VPS.

Kristaps Bērziņš