Node.js (generic) on CloudMagnus
A bare Node site with an allocated upstream port and a placeholder server. Bring your own framework. The platform handles the nginx reverse-proxy, SSL, the upstream port allocation, and the per-site Linux user; what runs on the port is yours.
What our scaffold installs
npm init -yin yourhtdocs/(creates a defaultpackage.json)- A placeholder
server.jsusing only Node's built-inhttpmodule, listening on127.0.0.1:<allocatedPort>and responding "Node.js site ready on port <X>" - An allocated port from the 3000-3999 pool, unique per site, shown on the site detail page
- nginx vhost configured to reverse-proxy your domain to
127.0.0.1:<allocatedPort>with WebSocket upgrade headers passed through - No database. Generic Node apps don't get one because we don't know which DB you'd want. Contact support for MySQL or Postgres provisioned alongside.
What you do next
- SSH in (Site Detail → SFTP credentials work for SSH too).
- Replace
server.jswith your own server, or install a framework:npm install express(or fastify, koa, hono, whatever you prefer). - Make sure your server listens on
127.0.0.1:<allocatedPort>, not0.0.0.0or a different port. nginx is hardcoded to talk to that exact upstream. - Start your server under a process manager (see below).
Where your files live
/home/<site-user>/
├── htdocs/ ← your app root
│ ├── server.js ← entrypoint (replace with your own)
│ ├── package.json
│ ├── package-lock.json
│ └── node_modules/
└── logs/ ← nginx logs (your Node logs go where you direct them)
The allocated port
Each Node / Next.js / Python site gets a unique port from the 3000-3999 pool, allocated at provisioning time. The port number is stored on your site row and shown on the site detail page. Your app must listen on that exact port, on 127.0.0.1 (loopback) — binding to 0.0.0.0 isn't dangerous (the host firewall blocks external traffic) but it's wasted scope.
If you ever need to know the port from inside your app, hardcode it from the dashboard, or set it via env: PORT=<allocatedPort> node server.js and read process.env.PORT. Express/Fastify/Koa all expose this pattern.
Process management
The placeholder server.js exits when your SSH session disconnects, which means your site stops responding. For real use you need a process manager. Three options:
- pm2 (most common):
For survive-reboot behavior, set upnpm install pm2 ./node_modules/.bin/pm2 start server.js --name app ./node_modules/.bin/pm2 savepm2 startupvia the user crontab. The standardpm2 startupwrites a systemd unit that needs sudo to install; if you don't have that on your tier, the cron alternative is@reboot ~/.npm-packages/bin/pm2 resurrect. - nohup (simplest):
Survives logout. Doesn't auto-restart on crash.nohup node server.js > ~/logs/app.log 2>&1 & - systemd user units: available if support has enabled
loginctl enable-lingerfor your user. Drop a unit at~/.config/systemd/user/app.serviceandsystemctl --user enable --now app. Best long-term answer.
A managed-process UI in the dashboard is on the roadmap. For now, SSH-side management is the path.
Node version
The wizard offers Node 18, 20, or 22 LTS. Pick at site creation. Change later from Site Detail → Overview → Node.js Version. Switches restart your nginx vhost (no app process restart — you handle that).
To use a Node version not in the dropdown (e.g. 21 for testing): install nvm in your home directory (curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash), use whichever version you want, and have your process manager invoke nvm use 21 && node server.js.
npm install on deploy
Not automatic. SSH in after uploading new code and run npm install (or npm ci for clean reproducible installs from package-lock.json). If you want zero-touch, build your node_modules/ locally and ship it via SFTP (less common; more common to run install on the server).
Deploying changes
The standard pattern:
- Push code to your git repo or SFTP-upload changed files
- SSH in:
cd htdocs && git pull && npm ci - Restart your process:
pm2 reload app
For zero-downtime restarts, pm2's reload (vs restart) does a rolling restart of cluster-mode workers. For a single-process setup, there's a brief blip; usually acceptable.
Environment variables
Two paths:
- Process manager env: pm2 takes
--envoptions or anecosystem.config.jswith environment objects. systemd user units takeEnvironment=directives. - dotenv: install
dotenv, drop a.envfile in your project root with mode 600, andrequire('dotenv').config()at the top of your entrypoint. Standard Node pattern.
Don't commit .env to git. Use .env.example for shape and ship the real file out-of-band.
Logs
~/logs/nginx/error.log ← nginx errors (502s when your app is down)
~/logs/nginx/access.log ← request log
~/.pm2/logs/ ← pm2 stdout/stderr per app (if using pm2)
<wherever you redirect> ← if using nohup/systemd
For live tailing during a deploy: pm2 logs app --lines 200 shows recent + live.
Migrating from another host
- Add the site on CloudMagnus, get your allocated port.
- Adjust your app to listen on the allocated port (or read
process.env.PORT). - SSH in, clone/upload your code,
npm ci. - Start your process under a manager.
- Switch DNS.
If you came from Heroku or Render, the main difference is process management — those platforms run your web: node server.js Procfile entry automatically; here you run pm2 / systemd / nohup yourself. Otherwise the porting is straightforward Node.
Common Node issues
502 Bad Gateway
Your Node process isn't listening on the expected port, or it crashed. Check:
pm2 list— is your app shown asonline?ss -tnlp— is something listening on127.0.0.1:<port>?~/logs/nginx/error.log— what does nginx say it tried to reach?
If pm2 shows errored, pm2 logs app shows the crash trace.
WebSocket connections drop
nginx is configured to upgrade WebSocket connections by default. If you're seeing drops, it's almost always your application timing out the connection (heartbeat / ping issues) or your code not handling the upgrade event. Test with wscat from your laptop to isolate.
"Cannot find module"
npm install didn't run after you uploaded new dependencies. SSH in and run it.
Process keeps dying after a few requests
Usually a memory leak or unhandled exception killing the process. pm2 will auto-restart by default, masking the real issue. Check pm2 logs app --err for the actual stack trace.
EADDRINUSE on startup
Another instance of your app is already on the port. ss -tnlp shows the PID; kill it first. Often happens after a botched nohup deploy where the old process didn't terminate.
High response latency, low CPU
Likely waiting on an external service (database, third-party API). Add timing logs around your hot paths to find which call is slow. Node's perf_hooks module is built in and zero-config.
What CloudMagnus doesn't do for Node.js
- Run
npm installautomatically. Manual after each deploy. - Manage your process. We give you the port; you keep something listening on it.
- Build your code. Build locally or in CI.
- Provide a database. Generic Node sites don't get one (Next.js, WordPress, Laravel, Django all do because the framework dictates the choice).
Going further
For finer-grained nginx tuning (proxy buffer sizes for streaming responses, request body size limits, custom timeouts), open CloudPanel from the site detail page. The wizard ships sensible defaults; everything beyond is in CP's vhost editor.