Дакументацыя даступная на англійскай і рускай мовах. Гэта англійская версія.

Node.js

The Node.js handler runs your app (Express, Fastify, Next.js, Nuxt, an API server or anything else that listens on a port) and passes the site’s requests to it. WardPane provides the Node.js runtime, the PM2 process manager, a systemd sandbox, HTTPS, the protection checks and static file serving.

How it fits together

visitor -> WardPane on 80/443 -> protection checks -> file in public/? -> served directly
                                                    -> otherwise      -> your app on 127.0.0.1:<port>
  • Each Node.js site gets its own port, which the panel assigns from the range 10000 to 19999. Your app listens on it on 127.0.0.1, so nobody can reach the app from outside except through WardPane.
  • Apps run under PM2. Each owner has one PM2 daemon in the systemd unit wardpane-pm2@<owner>.service, and every Node.js site of that owner is one PM2 app in it.
  • The app runs as the site’s owner (by default www-root), inside a systemd sandbox.

Folder layout

The site folder is the app folder. Put package.json there, next to a public/ folder:

/var/www/www-root/data/www/example.com/
  package.json
  package-lock.json
  server.js
  node_modules/
  public/            served directly as files
    favicon.ico
    css/app.css

WardPane serves files directly only from public/. Your code, node_modules and .env files stay in the site folder where no visitor can download them.

For a site from before 2.0 the app folder is /var/www/<domain>, with package.json next to its public/.

Setting up an app

  1. Create the site with the Node.js handler (or switch an existing site on its Handler tab). In the create dialog you can pick the Node.js version right away.
  2. Upload the app into the site folder over SFTP or with git clone from the shell.
  3. On the Handler tab, choose how the app starts and add environment variables.
  4. Press Save changes. The panel assigns a port, downloads Node.js if needed and starts the app.
  5. Press npm install to install the dependencies, then Restart.

The “Node.js app” card at the top of the Handler tab shows the state of the app (running, stopped, failed, starting), the exact Node.js version and the port.

Settings

You will find all of these on the site’s Handler tab.

Node.js version

Choose a Node.js major. WardPane 2.0 offers 20, 22, 24 and 26; new sites get 24. Node.js 20 reached its end of support in April 2026, so use 22 or newer for new apps.

The newest release of the chosen line is downloaded the first time a site needs it, see Runtimes. The line is set per site, so two apps of the same owner can run on different Node.js versions.

Start command

There are two ways to start the app:

  • npm script: a script from package.json, for example start. Press Read package.json to list the scripts of the app and pick one.
  • Entry file: a JavaScript file that starts the server, for example server.js, relative to the site folder. .js, .mjs and .cjs files work.

Environment variables

Add up to 20 variables. Names are letters, digits and _, starting with a letter or _; values can be up to 4096 characters.

The panel sets some variables itself:

Variable Value
PORT The port the panel assigned to the site. Your app must listen on it.
HOST 127.0.0.1. Your app must listen on this address.
NODE_ENV production, unless you set another value.

PORT, HOST, PATH, HOME and PM2_HOME cannot be overridden. The panel stores the values as plain text, and anyone who can open the site’s settings can read them.

Port

The panel assigns the port when the site first becomes a Node.js site. No other site gets the same port, and it never changes, even if you switch the site to another handler and back. The field is read-only.

Serve files from public/ directly

On by default. Files that exist in public/ are sent by WardPane itself (with ETags and resumable downloads, like on a static site), and everything else goes to the app. Turn it off if the app should see every request, including those for static files.

Limits

Setting Default Range
Memory limit, MB 512 64 to 16384
CPU limit, % 100 (one core) 10 to 800

When the app grows above its memory limit, PM2 restarts it. The owner’s PM2 unit gets the sum of its apps’ limits (plus a little for PM2 itself) as its systemd memory limit and CPU quota.

Block outgoing connections

With this on, the app can only talk to this server. Calls to external APIs, sending mail and downloads all fail. All Node.js apps of one owner share one PM2 daemon, so the block takes effect only when every Node.js app of that owner has it on.

PM2 from the shell

PM2 keeps its state in the owner’s home (/var/www/<owner>/data/.pm2), so the usual commands work from that owner’s shell, for example in the panel’s shell client:

pm2 list                    # the owner's apps, named after their domains
pm2 restart example.com     # restart one app
pm2 logs example.com        # follow its output

Change the version, start command, environment and limits in the panel. The panel regenerates the PM2 configuration from its own settings and would overwrite changes made by hand.

The proxy

Requests that are not served from public/ go to http://127.0.0.1:<port>:

  • They pass every protection check of the site first (Bot Fight, Geo, rate limits, required URL parameters, the site password), at the same point where a PHP site hands the request to PHP.
  • Bodies are streamed in both directions.
  • X-Forwarded-For, X-Real-IP, X-Forwarded-Proto and X-Forwarded-Host carry the real visitor address, scheme and host, also behind Cloudflare. Whatever the client itself sends in these headers (and in Forwarded) is dropped. In Express, trust the local proxy with app.set('trust proxy', 'loopback') to get the visitor IP in req.ip.
  • WebSockets work. An upgrade request goes through the same protection checks as any other request before it reaches the app.
  • Hop-by-hop headers are removed, and X-Powered-By is dropped from the app’s responses.
  • If the app is down or does not listen on its port, visitors get a plain 502 Bad Gateway page with no details about the server. If the app does not start answering in 2 minutes, the answer is 504. The panel log gets the reason, at most once a minute per site.
  • Request bodies up to 100 MB are passed to the app; larger ones get 413.

Restart and npm install

The “Node.js app” card has two buttons:

  • Restart restarts the app in PM2.
  • npm install installs the dependencies: npm ci if the folder has a package-lock.json, otherwise npm install. It runs as the owner, in a sandbox, with the site’s own Node.js version, and is stopped after 10 minutes. The output goes to the npm-install.log of the site.

Logs

The Logs card on the Handler tab shows three files: Output (node-out.log), Errors (node-error.log) and npm install (npm-install.log).

Layout Where the logs are
New sites (2.0) /var/www/<owner>/data/logs/<domain>-node-out.log, <domain>-node-error.log, <domain>-npm-install.log
Sites from before 2.0 /var/www/<domain>/logs/node-out.log, node-error.log, npm-install.log

Runtimes and security

  • Node.js builds are downloaded from https://nodejs.org/dist/ (linux-x64 or linux-arm64) and checked against the published SHASUMS256.txt before they are unpacked. They live in /opt/wardpane-runtimes. When no site uses a version any more, it is removed.
  • install.sh installs a pinned version of PM2.
  • Code from a runtime never runs as root. The root helper only downloads and unpacks it; the apps and npm run as the owner.
  • The PM2 unit runs in a systemd sandbox: the system is read-only, only the owner’s folders are writable, no new privileges, a private /tmp.

Deploying an Express app

  1. Create the site api.example.com with the Node.js handler and version 24. Point its DNS to the server first, so the certificate is issued right away.

  2. In the app, listen on the port and address the panel gives:

    const express = require('express');
    const app = express();
    app.set('trust proxy', 'loopback');
    app.get('/api/hello', (req, res) => res.json({ hello: req.ip }));
    app.listen(process.env.PORT, process.env.HOST);
    
  3. Add a start script to package.json:

    {
      "name": "api",
      "scripts": { "start": "node server.js" },
      "dependencies": { "express": "^4.21.0" }
    }
    
  4. Upload package.json, package-lock.json, server.js and a public/ folder into the site folder (for the default owner: /var/www/www-root/data/www/api.example.com).

  5. On the Handler tab, choose npm script and start (or press Read package.json and pick it), then Save changes.

  6. Press npm install and wait until it finishes (watch the npm install log).

  7. Press Restart. The status card should say the app is running; open https://api.example.com/api/hello.

If the site shows 502, look at the Errors log. Most often a dependency is missing, the start script has a typo, or the app listens on a fixed port instead of PORT.