# solidweb.app on 92.205.60.157 v4
###### tags: `melvin` `matthias` `solid`
# solidweb.app — Full Server Setup Guide
**Server:** `92.205.60.157` · **OS:** Debian 12 Bookworm
**Domain:** `solidweb.app`
**Stack:** Debian Bookworm · nvm · Node.js 24.11.0 · PM2 · JavaScript Solid Server (JSS) · Nginx · Let's Encrypt (wildcard) · Netdata · Uptime Kuma
> **Credits:** The JavaScript Solid Server (JSS) is created by
> [Melvin Carvalho](https://melvin.me/) — web pioneer, mathematician, Solid enthusiast,
> and long-time contributor to the Solid ecosystem and decentralised web.
---
## Table of Contents
1. [Architecture Overview](#1-Architecture-Overview)
2. [DNS Setup](#2-DNS-Setup)
3. [Server Preparation](#3-Server-Preparation)
4. [Node.js via nvm](#4-Nodejs-via-nvm)
5. [PM2 Installation](#5-PM2-Installation)
6. [JavaScript Solid Server (JSS) — Subdomain Pod Mode](#6-JavaScript-Solid-Server-JSS-—-Subdomain-Pod-Mode)
7. [Uptime Kuma](#7-Uptime-Kuma)
8. [PM2 Ecosystem File & Boot Hook](#8-PM2-Ecosystem-File-amp-Boot-Hook)
9. [Nginx Installation & HTTP Scaffolding](#9-Nginx-Installation-amp-HTTP-Scaffolding)
10. [Let's Encrypt Wildcard Certificate (DNS-01)](#10-Lets-Encrypt-Wildcard-Certificate-DNS-01)
11. [Nginx HTTPS Final Config](#11-Nginx-HTTPS-Final-Config)
12. [Netdata](#12-Netdata)
13. [Firewall Rules](#13-Firewall-Rules)
14. [Nginx Virtual Host Summary](#14-Nginx-Virtual-Host-Summary)
15. [Post-Install Checklist](#15-Post-Install-Checklist)
16. [Maintenance & Useful Commands](#16-Maintenance-amp-Useful-Commands)
[Summary: Port & Service Map](#Summary-Port-amp-Service-Map)
[Credits](#Credits)
---
## 1. Architecture Overview
```
Internet
│
▼
92.205.60.157 :80 / :443
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Nginx (reverse proxy + TLS termination, wildcard cert) │
│ │
│ solidweb.app → JSS :3000 (root / login / IDP) │
│ *.solidweb.app → JSS :3000 (per-user pods) │
│ status.solidweb.app → Uptime Kuma :3001 │
│ monitor.solidweb.app → Netdata :19999 │
└──────────────────────────────────────────────────────────────────┘
↑ ↑
PM2 (user: jss) PM2 (user: kuma)
manages JSS manages Uptime Kuma
pm2-jss.service pm2-kuma.service
(systemd unit, (systemd unit,
auto-generated auto-generated
by PM2) by PM2)
```
**Process management strategy:**
- **PM2** manages JSS and Uptime Kuma — one PM2 daemon per service user (`jss`, `kuma`).
- PM2's `startup` command auto-generates a systemd unit for each user so both services
survive reboots without hand-written unit files.
- **Netdata** keeps its own native systemd service (it is not a Node.js process).
- **Nginx** keeps its own native systemd service.
- All Node.js services bind to `127.0.0.1` only; Nginx is the sole public gateway.
- **Registration is open** — anyone can create a pod at `<username>.solidweb.app`.
> **Why one PM2 instance per user and not a shared root PM2?**
> Running PM2 as root is a security anti-pattern. Separate per-user PM2 daemons give each
> service its own isolated process tree, log directory (`~/.pm2/logs`), and dump file
> (`~/.pm2/dump.pm2`). Each generates its own systemd unit (`pm2-jss.service`,
> `pm2-kuma.service`) that is managed independently.
---
## 2. DNS Setup
Create the following records at your DNS registrar (TTL 300 s is fine):
| Hostname | Type | Value | Purpose |
|------------------|------|-----------------|------------------------------|
| `solidweb.app` | A | `92.205.60.157` | Root domain / Solid IDP |
| `*.solidweb.app` | A | `92.205.60.157` | All user pods + subservices |
> One wildcard A record covers everything: `alice.solidweb.app`, `status.solidweb.app`,
> `monitor.solidweb.app`, etc.
> **Critical:** DNS must propagate fully before requesting the wildcard TLS certificate.
> Verify with:
> ```bash
> dig alice.solidweb.app +short # should return 92.205.60.157
> dig status.solidweb.app +short # should return 92.205.60.157
> ```
---
## 3. Server Preparation
```bash
# Update and upgrade
apt update && apt upgrade -y
# Install essential packages
apt install -y \
curl wget git \
build-essential \
ufw \
nginx \
certbot \
apache2-utils
# Set hostname
hostnamectl set-hostname solidweb
```
---
## 4. Node.js via nvm
### 4.1 Create dedicated service users
```bash
useradd --system --create-home --shell /bin/bash --home-dir /home/jss jss
useradd --system --create-home --shell /bin/bash --home-dir /home/kuma kuma
```
> Both users get `/bin/bash` so that nvm and PM2 can be installed into their home
> directories during setup. Services run non-interactively once PM2 is managing them.
### 4.2 Install nvm for both users
```bash
su - jss -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash'
su - kuma -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash'
```
### 4.3 Install Node.js 24.11.0 for both users
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && nvm install 24.11.0 && nvm alias default 24.11.0'
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && nvm install 24.11.0 && nvm alias default 24.11.0'
```
Verify:
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && node --version' # v24.11.0
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && node --version' # v24.11.0
```
---
## 5. PM2 Installation
### 5.1 Install PM2 globally for both users
PM2 must be installed into the same nvm-managed npm prefix as the Node version that will
run it. Never use `sudo npm install -g pm2` — that installs into the system npm, not the
nvm one, causing PATH mismatches at boot.
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && npm install -g pm2'
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && npm install -g pm2'
```
Verify:
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && pm2 --version'
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && pm2 --version'
```
### 5.2 Install pm2-logrotate (prevents unbounded log growth)
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && pm2 install pm2-logrotate'
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && pm2 install pm2-logrotate'
```
---
## 6. JavaScript Solid Server (JSS) — Subdomain Pod Mode
### 6.1 Install JSS
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && npm install -g javascript-solid-server'
```
### 6.2 Create the data directory
```bash
mkdir -p /var/lib/jss/data
chown -R jss:jss /var/lib/jss
```
### 6.3 Config file
```bash
mkdir -p /etc/jss
```
Create `/etc/jss/config.json`:
```json
{
"port": 3000,
"host": "127.0.0.1",
"root": "/var/lib/jss/data",
"subdomains": true,
"baseDomain": "solidweb.app",
"conneg": true,
"notifications": true,
"idp": true,
"idpIssuer": "https://solidweb.app",
"mashlibCdn": true,
"defaultQuota": "1GB"
}
```
> **Key settings:**
>
> - `"subdomains": true` — pod `alice` lives at `alice.solidweb.app`, not `/alice/`.
> - `"baseDomain": "solidweb.app"` — required for JSS to construct correct pod/WebID URIs.
> No leading wildcard, no trailing slash.
> - `"idpIssuer": "https://solidweb.app"` — the Identity Provider always lives at the root
> domain. No trailing slash — must be exact.
> - `"host": "127.0.0.1"` — loopback only; Nginx handles all public traffic.
> - `"mashlibCdn": true` — loads the SolidOS data browser from unpkg CDN; no local build.
> - `"conneg": true` — enables Turtle ↔ JSON-LD content negotiation for client compatibility.
> - `"defaultQuota": "1GB"` — per-pod storage limit; adjust as needed.
> - **No `inviteOnly` key** — omitting it (or setting it to `false`) leaves registration
> fully open. Anyone visiting `https://solidweb.app` can create a pod.
```bash
chown -R jss:jss /etc/jss
```
### 6.4 Quick sanity test (before PM2)
```bash
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && jss start --config /etc/jss/config.json'
# Confirm "Server listening on 127.0.0.1:3000", then Ctrl+C
```
---
## 7. Uptime Kuma
### 7.1 Install
```bash
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && npm install -g uptime-kuma'
```
### 7.2 Create data directory
```bash
mkdir -p /var/lib/kuma
chown -R kuma:kuma /var/lib/kuma
```
### 7.3 Quick sanity test (before PM2)
```bash
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && uptime-kuma-server \
--data-dir /var/lib/kuma --port 3001 --host 127.0.0.1'
# Confirm "Server started on port 3001", then Ctrl+C
```
> Uptime Kuma has **no default password**. You create your admin account on the first
> browser visit to `https://status.solidweb.app`.
---
## 8. PM2 Ecosystem File & Boot Hook
This is the most critical section. Read it fully before executing.
### 8.1 PM2 ecosystem file for JSS
Create `/etc/jss/ecosystem.config.js`:
```js
module.exports = {
apps: [
{
name: 'jss',
// Full absolute path — PM2 at boot does not source .bashrc and cannot
// resolve the nvm shim, so the versioned path must be explicit.
script: '/home/jss/.nvm/versions/node/v24.11.0/bin/jss',
args: 'start --config /etc/jss/config.json',
cwd: '/var/lib/jss',
// Fork mode is correct for JSS — cluster mode is for stateless HTTP apps
exec_mode: 'fork',
instances: 1,
autorestart: true,
watch: false, // never watch in production
max_restarts: 10,
min_uptime: '5s', // must stay alive 5 s to count as a clean start
restart_delay: 4000, // wait 4 s between restart attempts
// Restart if JSS exceeds 512 MB
max_memory_restart: '512M',
out_file: '/home/jss/.pm2/logs/jss-out.log',
error_file: '/home/jss/.pm2/logs/jss-error.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
env_production: {
NODE_ENV: 'production',
PATH: '/home/jss/.nvm/versions/node/v24.11.0/bin:' + process.env.PATH,
},
},
],
};
```
```bash
chown jss:jss /etc/jss/ecosystem.config.js
```
### 8.2 PM2 ecosystem file for Uptime Kuma
Create `/home/kuma/ecosystem.config.js`:
```js
module.exports = {
apps: [
{
name: 'uptime-kuma',
script: '/home/kuma/.nvm/versions/node/v24.11.0/bin/uptime-kuma-server',
args: '--data-dir /var/lib/kuma --port 3001 --host 127.0.0.1',
cwd: '/var/lib/kuma',
exec_mode: 'fork',
instances: 1,
autorestart: true,
watch: false,
max_restarts: 10,
min_uptime: '5s',
restart_delay: 4000,
max_memory_restart: '256M',
out_file: '/home/kuma/.pm2/logs/uptime-kuma-out.log',
error_file: '/home/kuma/.pm2/logs/uptime-kuma-error.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
env_production: {
NODE_ENV: 'production',
PATH: '/home/kuma/.nvm/versions/node/v24.11.0/bin:' + process.env.PATH,
},
},
],
};
```
```bash
chown kuma:kuma /home/kuma/ecosystem.config.js
```
### 8.3 Start both apps under PM2
```bash
sudo -u jss bash -c '
source /home/jss/.nvm/nvm.sh
pm2 start /etc/jss/ecosystem.config.js --env production
pm2 status
'
sudo -u kuma bash -c '
source /home/kuma/.nvm/nvm.sh
pm2 start /home/kuma/ecosystem.config.js --env production
pm2 status
'
```
Expected from `pm2 status`:
```
┌────┬────────────────┬──────┬───────────┬──────────┐
│ id │ name │ mode │ pid │ status │
├────┼────────────────┼──────┼───────────┼──────────┤
│ 0 │ jss │ fork │ 12345 │ online │
└────┴────────────────┴──────┴───────────┴──────────┘
```
### 8.4 Register PM2 startup hooks (critical — follow this order exactly)
PM2 generates a systemd unit containing the exact `PATH` with the nvm bin directory.
**You must run `pm2 startup` as the service user, then copy-paste the printed
`sudo env PATH=...` command and run it as root.** This two-step is mandatory — skipping
it or running `pm2 startup` directly as root produces a broken PATH at boot.
#### For the `jss` user:
```bash
# Step 1 — run as jss; this prints the sudo command to copy
sudo -u jss bash -c \
'source /home/jss/.nvm/nvm.sh && pm2 startup systemd -u jss --hp /home/jss --service-name pm2-jss'
```
PM2 outputs something like:
```
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/home/jss/.nvm/versions/node/v24.11.0/bin \
/home/jss/.nvm/versions/node/v24.11.0/lib/node_modules/pm2/bin/pm2 \
startup systemd -u jss --hp /home/jss --service-name pm2-jss
```
```bash
# Step 2 — copy the EXACT output above and run it as root:
sudo env PATH=$PATH:/home/jss/.nvm/versions/node/v24.11.0/bin \
/home/jss/.nvm/versions/node/v24.11.0/lib/node_modules/pm2/bin/pm2 \
startup systemd -u jss --hp /home/jss --service-name pm2-jss
```
#### For the `kuma` user:
```bash
# Step 1
sudo -u kuma bash -c \
'source /home/kuma/.nvm/nvm.sh && pm2 startup systemd -u kuma --hp /home/kuma --service-name pm2-kuma'
# Step 2 — copy the exact output and run as root:
sudo env PATH=$PATH:/home/kuma/.nvm/versions/node/v24.11.0/bin \
/home/kuma/.nvm/versions/node/v24.11.0/lib/node_modules/pm2/bin/pm2 \
startup systemd -u kuma --hp /home/kuma --service-name pm2-kuma
```
### 8.5 Save both PM2 process lists
`pm2 startup` only registers the boot hook. `pm2 save` writes the dump file that lists
which processes to resurrect. **Both steps are mandatory.**
```bash
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 save'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 save'
```
### 8.6 Verify the generated systemd units
```bash
systemctl status pm2-jss.service
systemctl status pm2-kuma.service
# Inspect the generated unit files
systemctl cat pm2-jss.service
systemctl cat pm2-kuma.service
```
Both units should be `active (running)` and have `WantedBy=multi-user.target`.
---
## 9. Nginx Installation & HTTP Scaffolding
### 9.1 Remove default site
```bash
rm -f /etc/nginx/sites-enabled/default
mkdir -p /var/www/certbot
```
### 9.2 Temporary HTTP vhost
For wildcard certs via DNS-01 the `/.well-known/acme-challenge/` webroot is not needed.
Set up a minimal catch-all redirect now; HTTPS blocks follow after cert issuance.
Create `/etc/nginx/sites-available/solidweb.app`:
```nginx
server {
listen 80;
listen [::]:80;
server_name solidweb.app *.solidweb.app;
return 301 https://$host$request_uri;
}
```
```bash
ln -s /etc/nginx/sites-available/solidweb.app /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
```
---
## 10. Let's Encrypt Wildcard Certificate (DNS-01)
### Why DNS-01?
Wildcard certificates (`*.solidweb.app`) **cannot** be issued via the HTTP-01 challenge.
Let's Encrypt mandates the **DNS-01** challenge for wildcards.
### One certificate covers everything
| Covers | Path |
|------------------------------------|-----------------------------------------|
| `solidweb.app` + `*.solidweb.app` | `/etc/letsencrypt/live/solidweb.app/` |
### 10.1 Request the wildcard certificate
```bash
certbot certonly \
--manual \
--preferred-challenges dns \
--server https://acme-v02.api.letsencrypt.org/directory \
--agree-tos \
--email you@example.com \
-d solidweb.app \
-d '*.solidweb.app'
```
### 10.2 Add TXT records at your registrar (twice — do not skip the second)
You will be prompted **twice** — once per SAN.
| Name | Type | Value |
|--------------------------------|------|----------------------------------|
| `_acme-challenge.solidweb.app` | TXT | `<first token>` |
| `_acme-challenge.solidweb.app` | TXT | `<second token>` |
> Both TXT records must coexist. Do **not** delete the first before adding the second.
### 10.3 Verify propagation before pressing Enter
```bash
dig TXT _acme-challenge.solidweb.app +short
# Both token values must appear before you press Enter in the Certbot terminal
```
### 10.4 Automate renewal with a DNS plugin
The manual method does not auto-renew. Re-issue with your registrar's Certbot DNS plugin:
```bash
# Example: Cloudflare
apt install -y python3-certbot-dns-cloudflare
cat > /etc/letsencrypt/cloudflare.ini <<'EOF'
dns_cloudflare_api_token = YOUR_API_TOKEN_HERE
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
--server https://acme-v02.api.letsencrypt.org/directory \
--agree-tos \
--email you@example.com \
-d solidweb.app \
-d '*.solidweb.app'
```
> Other DNS provider plugins: https://certbot.eff.org/docs/using.html#dns-plugins
### 10.5 Nginx reload hook on renewal
```bash
cat > /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh <<'EOF'
#!/bin/bash
systemctl reload nginx
EOF
chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
systemctl status certbot.timer
certbot renew --dry-run
```
---
## 11. Nginx HTTPS Final Config
### 11.1 Shared TLS snippet
Create `/etc/nginx/snippets/ssl-params.conf`:
```nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
```
### 11.2 status.solidweb.app (Uptime Kuma)
Create `/etc/nginx/sites-available/status.solidweb.app`:
```nginx
server {
listen 80;
listen [::]:80;
server_name status.solidweb.app;
return 301 https://status.solidweb.app$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name status.solidweb.app;
ssl_certificate /etc/letsencrypt/live/solidweb.app/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/solidweb.app/privkey.pem;
include snippets/ssl-params.conf;
# Uptime Kuma requires WebSocket for real-time dashboard updates
location / {
proxy_pass http://127.0.0.1:3001;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}
```
### 11.3 monitor.solidweb.app (Netdata)
```bash
htpasswd -c /etc/nginx/.htpasswd admin
```
Create `/etc/nginx/sites-available/monitor.solidweb.app`:
```nginx
server {
listen 80;
listen [::]:80;
server_name monitor.solidweb.app;
return 301 https://monitor.solidweb.app$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name monitor.solidweb.app;
ssl_certificate /etc/letsencrypt/live/solidweb.app/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/solidweb.app/privkey.pem;
include snippets/ssl-params.conf;
auth_basic "Netdata — restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:19999;
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;
}
location ~ ^/api/v[0-9]+/stream {
proxy_pass http://127.0.0.1:19999;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
### 11.4 solidweb.app + all pod subdomains (JSS)
Overwrite `/etc/nginx/sites-available/solidweb.app`:
```nginx
# ─── HTTP → HTTPS redirect ────────────────────────────────────────────────────
server {
listen 80;
listen [::]:80;
server_name solidweb.app *.solidweb.app;
return 301 https://$host$request_uri;
}
# ─── HTTPS: root + all pod subdomains → JSS ──────────────────────────────────
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
# Catches solidweb.app itself AND every *.solidweb.app subdomain not caught
# by a more-specific exact-match server block (status.*, monitor.*).
# Nginx resolves exact server_name matches before wildcard ones on the same port.
server_name solidweb.app *.solidweb.app;
ssl_certificate /etc/letsencrypt/live/solidweb.app/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/solidweb.app/privkey.pem;
include snippets/ssl-params.conf;
client_max_body_size 512m;
# WebSocket: JSS real-time notifications (solid-0.1 protocol)
location ~ ^/\.notifications {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
# WebSocket: Nostr relay (if --nostr enabled in JSS)
location ~ ^/relay {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
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 X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
```
### 11.5 Enable all sites and reload
```bash
ln -s /etc/nginx/sites-available/status.solidweb.app /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/monitor.solidweb.app /etc/nginx/sites-enabled/
# solidweb.app was already linked in step 9
nginx -t && systemctl reload nginx
```
---
## 12. Netdata
Netdata is not a Node.js process and is managed by its own native systemd service — PM2 is
not involved.
### 12.1 Install
```bash
wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh
sh /tmp/netdata-kickstart.sh --dont-start-it --stable-channel
```
### 12.2 Bind to localhost only
Edit `/etc/netdata/netdata.conf`:
```ini
[web]
bind to = 127.0.0.1:19999
```
### 12.3 Start and enable
```bash
systemctl enable --now netdata
systemctl status netdata
```
### 12.4 Verify
```bash
curl -s http://127.0.0.1:19999/api/v1/info | python3 -m json.tool | head -20
```
---
## 13. Firewall Rules
```bash
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enable
ufw status verbose
```
> Ports 3000, 3001, and 19999 remain closed to the public — reachable only from
> `127.0.0.1` via Nginx.
---
## 14. Nginx Virtual Host Summary
| Incoming Request | Nginx match | Proxied to | Auth |
|--------------------------------|--------------------------|---------------------|---------------------|
| `https://solidweb.app` | exact `solidweb.app` | JSS `:3000` | Solid-OIDC / WAC |
| `https://alice.solidweb.app` | wildcard `*.solidweb.app`| JSS `:3000` | Solid-OIDC / WAC |
| `https://status.solidweb.app` | exact (higher priority) | Uptime Kuma `:3001` | Kuma login + 2FA |
| `https://monitor.solidweb.app` | exact (higher priority) | Netdata `:19999` | HTTP Basic Auth |
| `http://*` | all | → 301 HTTPS | — |
---
## 15. Post-Install Checklist
### PM2 status
```bash
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 status'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 status'
systemctl status pm2-jss.service
systemctl status pm2-kuma.service
```
### JSS — open registration
```bash
# Root domain
curl -I https://solidweb.app
# Expected: HTTP/2 200
# Pod subdomain (200 or 401 — both confirm routing is working)
curl -I https://alice.solidweb.app/
# Confirm subdomain mode: podUri must be at the subdomain, not a path
curl -s https://solidweb.app/.pods \
-X POST \
-H "Content-Type: application/json" \
-d '{"name":"testpod"}' | python3 -m json.tool
# "podUri" should be "https://testpod.solidweb.app/"
# WebSocket notifications header
curl -I https://alice.solidweb.app/public/
# Should contain: Updates-Via: wss://alice.solidweb.app/.notifications
```
### Wildcard certificate
```bash
echo | openssl s_client \
-connect solidweb.app:443 \
-servername solidweb.app 2>/dev/null \
| openssl x509 -noout -text \
| grep -A2 "Subject Alternative Name"
# Expected: DNS:solidweb.app, DNS:*.solidweb.app
```
### Uptime Kuma
1. Open `https://status.solidweb.app`.
2. Create admin account (no default password — first-run wizard).
3. Enable **2FA** in Settings → Security.
4. Add monitors:
- **HTTP(s):** `https://solidweb.app` — interval 60 s
- **HTTP(s):** `https://alice.solidweb.app/` — interval 60 s
- **HTTP(s):** `https://status.solidweb.app` — interval 60 s
- **HTTP(s):** `https://monitor.solidweb.app` — interval 60 s
- **SSL Certificate:** `solidweb.app` — alert 14 days before expiry
5. Create a public **Status Page**.
### Netdata
```bash
# Open https://monitor.solidweb.app and log in with htpasswd credentials
curl -s -u admin:yourpassword https://monitor.solidweb.app/api/v1/info | head -5
```
### TLS dates
```bash
for host in solidweb.app status.solidweb.app monitor.solidweb.app; do
echo "=== $host ==="
echo | openssl s_client -connect "$host:443" 2>/dev/null \
| openssl x509 -noout -dates
done
```
---
## 16. Maintenance & Useful Commands
### PM2 daily operations
```bash
# Interactive live dashboard
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 monit'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 monit'
# Tail logs
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 logs jss'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 logs uptime-kuma'
# Graceful reload (zero-downtime for networked apps)
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 reload jss'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 reload uptime-kuma'
# Hard restart
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 restart jss'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 restart uptime-kuma'
# Stop
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 stop jss'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 stop uptime-kuma'
```
### Update JSS
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && npm update -g javascript-solid-server'
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 restart jss'
```
### Update Uptime Kuma
```bash
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && npm update -g uptime-kuma'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 restart uptime-kuma'
```
### Update Netdata
```bash
/usr/libexec/netdata/netdata-updater.sh
```
### Upgrade Node.js version
When you upgrade Node, the PM2 binary path changes. Per PM2 documentation, the startup
hook **must be re-run** after every Node version change.
```bash
NEW=24.12.0 # example
for USER in jss kuma; do
HOME_DIR="/home/$USER"
sudo -u $USER bash -c "
source $HOME_DIR/.nvm/nvm.sh
nvm install $NEW
nvm alias default $NEW
npm install -g pm2
"
done
# Re-run pm2 startup for each user; copy-paste the printed sudo env command as root
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 startup systemd -u jss --hp /home/jss --service-name pm2-jss'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 startup systemd -u kuma --hp /home/kuma --service-name pm2-kuma'
# Reload PM2 daemon in-memory without losing running processes
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 update'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 update'
# Save updated process lists
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 save'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 save'
```
### Update PM2 itself
```bash
su - jss -c 'source /home/jss/.nvm/nvm.sh && npm install -g pm2@latest && pm2 update'
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && npm install -g pm2@latest && pm2 update'
# Re-generate systemd units (binary path may have changed)
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && pm2 startup systemd -u jss --hp /home/jss --service-name pm2-jss'
sudo -u kuma bash -c 'source /home/kuma/.nvm/nvm.sh && pm2 startup systemd -u kuma --hp /home/kuma --service-name pm2-kuma'
# Copy-paste the printed sudo env ... command as root for each user
```
### Manage storage quotas
```bash
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && jss quota show alice'
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && jss quota set alice 2GB'
sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && jss quota reconcile alice'
```
### Manual certificate renewal
```bash
certbot renew --force-renewal
systemctl reload nginx
```
---
## Summary: Port & Service Map
| Service | Managed by | User | Port | Public URL |
|--------------|-------------------|--------|-------|---------------------------------------------------------|
| JSS | PM2 (`pm2-jss`) | `jss` | 3000 | `https://solidweb.app` + `https://*.solidweb.app` |
| Uptime Kuma | PM2 (`pm2-kuma`) | `kuma` | 3001 | `https://status.solidweb.app` |
| Netdata | systemd (native) | root | 19999 | `https://monitor.solidweb.app` |
| Nginx | systemd (native) | root | 80/443| All of the above |
---
## Credits
The **JavaScript Solid Server (JSS)** is created by
**[Melvin Carvalho](https://melvin.me/)** — web pioneer, mathematician, Solid Protocol
enthusiast, and long-time contributor to the decentralised web. Melvin previously ran
`solid.community`, one of the original public Solid pod communities, and has been a key
figure in the development of WebID, Solid, and linked data on the web.
- Website: https://melvin.me/
- GitHub: https://github.com/melvincarvalho
- npm: https://www.npmjs.com/~melvincarvalho
- JSS: https://github.com/JavaScriptSolidServer/JavaScriptSolidServer
---
*Generated for solidweb.app · 92.205.60.157 · Debian 12 Bookworm · Node.js 24.11.0 via nvm · PM2 · March 2026*