Node-RED is a flow-based programming tool built on Node.js: you build integrations in a browser editor by wiring together nodes that receive, transform and send messages, such as MQTT topics, HTTP requests, databases or JavaScript functions. In this tutorial you will install Node-RED on Ubuntu 24.04, run it as a systemd service under its own user, protect the editor with a login and HTTPS behind Nginx, and build two flows: a small HTTP API and an MQTT message processor.
Prerequisites
To follow this tutorial you need:
- A server running Ubuntu 24.04 LTS with at least 1 GB of RAM, for example a CubePath VPS.
- A non-root user with
sudoprivileges and UFW enabled with OpenSSH allowed. - A domain name such as
nodered.your_domainwith an A record pointing toyour_server_ip.
Step 1 - Installing Node.js 22 LTS
Ubuntu 24.04 ships Node.js 18, which is past its end of life. Install Node.js 22 LTS from the NodeSource repository instead. Import the repository key into its own keyring:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
Add the repository and install Node.js, which includes npm:
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt update
sudo apt install -y nodejs
Verify the versions:
node --version
npm --version
v22.x.x
10.x.x
Step 2 - Installing Node-RED
Install Node-RED globally with npm, as the Node-RED documentation recommends. The --unsafe-perm flag lets npm build native modules when running as root:
sudo npm install -g --unsafe-perm node-red
Check the installed version:
node-red --help | head -3
Node-RED v4.x.x
Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]
Create a system user for Node-RED and a directory for its flows, settings and extra nodes (its "user directory"):
sudo useradd --system --home-dir /var/lib/node-red --shell /usr/sbin/nologin nodered
sudo install -d -o nodered -g nodered -m 0750 /var/lib/node-red
Step 3 - Running Node-RED with systemd
Create a unit file:
sudo nano /etc/systemd/system/node-red.service
[Unit]
Description=Node-RED
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=nodered
Group=nodered
WorkingDirectory=/var/lib/node-red
Environment=NODE_ENV=production
Environment=NODE_OPTIONS=--max-old-space-size=512
ExecStart=/usr/bin/node-red --userDir /var/lib/node-red
Restart=on-failure
RestartSec=5
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target
--userDir points Node-RED at the directory you created, KillSignal=SIGINT lets it shut down cleanly, and --max-old-space-size caps the Node.js heap in megabytes; raise it on larger servers.
Start the service. On the first start, Node-RED writes a default settings.js into /var/lib/node-red:
sudo systemctl daemon-reload
sudo systemctl enable --now node-red
sudo journalctl -u node-red -n 20 --no-pager
... [info] Node-RED version: v4.x.x
... [info] Node.js version: v22.x.x
... [info] Settings file : /var/lib/node-red/settings.js
... [info] Server now running at http://127.0.0.1:1880/
UFW keeps port 1880 closed, which is what you want: the editor currently has no password.
Step 4 - Securing the editor
Generate a bcrypt hash for the admin password. The command prompts for the password and prints the hash:
node-red admin hash-pw
Password:
$2y$08$Qm1R3mS9...
Open the settings file:
sudo nano /var/lib/node-red/settings.js
The file contains commented-out examples of each option. Find and uncomment credentialSecret, uiHost and the adminAuth block, and set them as follows, pasting your hash:
credentialSecret: "a_long_random_string",
uiHost: "127.0.0.1",
adminAuth: {
type: "credentials",
users: [{
username: "admin",
password: "$2y$08$Qm1R3mS9...",
permissions: "*"
}]
},
credentialSecretencrypts the credentials (passwords, tokens) that flows store inflows_cred.json. Generate a value withopenssl rand -hex 32and keep a copy; without it you cannot decrypt the credentials after a restore.uiHost: "127.0.0.1"makes Node-RED listen only on localhost, so the only way in is through Nginx with TLS.adminAuthrequires a login for the editor and its admin API.
Restart the service and confirm the admin API now requires authentication:
sudo systemctl restart node-red
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:1880/flows
401
Step 5 - Publishing the editor with Nginx and HTTPS
Install Nginx and Certbot and allow web traffic:
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 'Nginx Full'
Create a server block. The editor keeps a WebSocket connection open to receive debug messages and status updates, so the upgrade headers are required:
sudo nano /etc/nginx/sites-available/node-red
server {
listen 80;
server_name nodered.your_domain;
location / {
proxy_pass http://127.0.0.1:1880;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}
Enable the site, check the syntax and get a certificate:
sudo ln -s /etc/nginx/sites-available/node-red /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d nodered.your_domain
Open https://nodered.your_domain and log in as admin with the password you hashed.
Step 6 - Building an HTTP endpoint flow
Node-RED can serve HTTP endpoints itself, which is handy for webhooks and small APIs. Build one with three nodes:
-
Drag an http in node onto the canvas. Set Method to
GETand URL to/hello. -
Drag a function node, connect the http in output to it and paste this code:
msg.payload = { message: "Hello from Node-RED", time: new Date().toISOString() }; return msg; -
Drag an http response node and connect the function output to it.
-
Click Deploy in the top right corner.
Endpoints created with http in nodes are not covered by adminAuth, so they are public unless you protect them. Test from any machine:
curl https://nodered.your_domain/hello
{"message":"Hello from Node-RED","time":"2026-09-25T10:41:07.215Z"}
To protect every HTTP endpoint with a single username and password, set httpNodeAuth in settings.js with a user and a bcrypt hash generated the same way as in Step 4, then restart Node-RED.
Step 7 - Processing MQTT messages
For this flow you need an MQTT broker. Install Mosquitto from the Ubuntu archive. With its default configuration it listens only on localhost, which is enough for Node-RED running on the same server:
sudo apt install -y mosquitto mosquitto-clients
sudo systemctl status mosquitto --no-pager
In the editor, build this flow:
-
Drag an mqtt in node. Double-click it, click the pencil next to Server, set Server to
localhostand Port to1883, and click Add. Set Topic tosensors/+/temperatureand Output to "a parsed JSON object". -
Drag a function node, connect it to the mqtt in node and paste:
const device = msg.topic.split("/")[1]; const celsius = Number(msg.payload.temperature); if (Number.isNaN(celsius)) { node.warn(`Invalid reading from ${device}`); return null; } msg.payload = { device: device, celsius: celsius, alert: celsius > 30, received: new Date().toISOString() }; return msg; -
Drag a debug node and connect the function to it.
-
Click Deploy. The mqtt in node shows a green "connected" status.
The + in the topic is a single-level wildcard, so one node handles every device. Returning null from a function drops the message. Publish a test reading from the server:
mosquitto_pub -h localhost -t sensors/garage/temperature -m '{"temperature": 32.5}'
Open the Debug tab in the right sidebar of the editor. You will see:
{"device":"garage","celsius":32.5,"alert":true,"received":"2026-09-25T10:45:12.004Z"}
From here you can wire the function to a switch node that routes alerts to an email or chat node, or to a database node that stores every reading.
Step 8 - Installing extra nodes
Nodes for databases, chat services and dashboards come from the npm registry. The safest way to install them is Menu > Manage palette > Install in the editor, which installs them into the user directory and loads them without a restart. For example, search for @flowfuse/node-red-dashboard to install Dashboard 2.0, whose UI is served at https://nodered.your_domain/dashboard.
You can also install nodes from the command line as the service user, then restart Node-RED:
cd /var/lib/node-red
sudo -u nodered npm install @flowfuse/node-red-dashboard
sudo systemctl restart node-red
Step 9 - Backing up your flows
Everything that defines your setup lives in the user directory: flows.json, flows_cred.json, settings.js and package.json (the list of installed nodes). Archive them regularly and copy the archive off the server:
sudo tar --exclude=node_modules -czf /root/node-red-$(date +%F).tar.gz -C /var/lib node-red
To restore on a new server, install Node-RED as above, extract the archive into /var/lib with sudo tar -xzf node-red-DATE.tar.gz -C /var/lib, fix ownership with sudo chown -R nodered:nodered /var/lib/node-red, run sudo -u nodered npm install in that directory to reinstall the nodes, and restart the service.
Troubleshooting
Node-RED stops starting after installing a node. Read the error with sudo journalctl -u node-red -n 50 --no-pager. Remove the offending package with sudo -u nodered npm uninstall <package> in /var/lib/node-red and restart.
The editor loads but shows "Lost connection to server". The WebSocket upgrade is not passing through Nginx. Check the Upgrade and Connection headers in the server block and reload Nginx.
settings.js changes break the service. A syntax error in the file stops Node-RED at startup. Check it before restarting:
sudo -u nodered node -e "require('/var/lib/node-red/settings.js')" && echo OK
Updating Node-RED. Update the global package and restart the service:
sudo npm install -g --unsafe-perm node-red
sudo systemctl restart node-red
Conclusion
Node-RED is now running on Ubuntu 24.04 as a dedicated systemd service, with a password-protected editor behind Nginx and HTTPS, an HTTP endpoint and an MQTT processing flow. Next, protect your HTTP endpoints with httpNodeAuth, enable the Projects feature in settings.js to keep flows in Git, and add Dashboard 2.0 to chart the readings your flows collect.
