solidweb.app setup v6

solidweb.app — Full Server Setup Guide (v6)

tags: melvin matthias solid jss debian trixie pm2 nginx letsencrypt

Server: 92.205.60.157 · OS: Debian 13 Trixie (stable, released 2025-08-09, latest point release 13.4 as of 2026-03-14)
Domain: solidweb.app
Stack: Debian 13 Trixie · nvm · Node.js 24.11.0 · PM2 · JavaScript Solid Server (JSS) · Nginx 1.26.x · Let's Encrypt (wildcard) · Certbot 4.0 · Netdata · Uptime Kuma

Credits: The JavaScript Solid Server (JSS) is created by
Melvin Carvalho — web pioneer, mathematician, Solid enthusiast,
and long-time contributor to the Solid ecosystem and decentralised web.


Table of Contents


0. Crosscheck Notes (v5 → v6)

This version reflects the only change: from Debian 12 Bookworm to Debian 13 Trixie.
Every component was re-verified against Trixie-specific sources.

Component Bookworm (v5) Trixie (v6) Impact
OS Debian 12 Bookworm (oldstable) Debian 13 Trixie (stable, 13.4) Name/version strings
Nginx (apt) 1.22.1 1.26.3 apt install nginx unchanged
Certbot (apt) 2.1 4.0 commands unchanged
python3-certbot-dns-cloudflare 2.x 4.0.0-2 (Trixie repo) apt install unchanged
gcc (system) 12.2 14.2 nvm builds fine
Node.js (system apt) 18.x 20.x (EOL April 2026) irrelevant — we use nvm
nvm v0.40.4 v0.40.4 unchanged
Node.js via nvm 24.11.0 24.11.0 unchanged
Netdata (Debian apt) in Bookworm repo removed from Trixie ⚠️ §12 updated — kickstart.sh only
UFW apt install ufw apt install ufw unchanged
PM2 / JSS / Uptime Kuma unchanged unchanged unchanged

Critical Trixie finding — Netdata: Debian removed Netdata from its Trixie
repositories because the project's web UI became closed-source. apt install netdata
fails with "package not found" on Trixie. The correct install path is Netdata's own
kickstart.sh, which installs from repository.netdata.cloud and supports Trixie.
Section 12 is updated accordingly.


1. Architecture Overview

Internet
│
▼
92.205.60.157 :80 / :443
│
▼
┌──────────────────────────────────────────────────────────────────┐
│  Nginx 1.26.x (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
   (auto-generated            (auto-generated
    by PM2 startup)            by PM2 startup)

Process management strategy:

Why one PM2 per user and not a shared root PM2? Running PM2 as root is a security
anti-pattern. Separate per-user PM2 daemons isolate each service's process tree, logs
(~/.pm2/logs), and dump file. Each generates its own systemd unit independently.


2. DNS Setup

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. No individual subdomain records needed.

Verify propagation before step 10:

dig alice.solidweb.app +short      # → 92.205.60.157
dig status.solidweb.app +short     # → 92.205.60.157
dig monitor.solidweb.app +short    # → 92.205.60.157

3. Server Preparation

apt update && apt upgrade -y

apt install -y \
  curl wget git \
  build-essential \
  ufw \
  nginx \
  certbot \
  apache2-utils

hostnamectl set-hostname solidweb

Trixie package versions confirmed:


4. Node.js via nvm

4.1 Create dedicated service users

useradd --system --create-home --shell /bin/bash --home-dir /home/jss  jss
useradd --system --create-home --shell /bin/bash --home-dir /home/kuma kuma

4.2 Install nvm for both users

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

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:

su - jss  -c 'source /home/jss/.nvm/nvm.sh  && node --version && npm --version'
su - kuma -c 'source /home/kuma/.nvm/nvm.sh && node --version && npm --version'
# Expected: v24.11.0

JSS requires Node.js 18+ (official docs). 24.11.0 is fully compatible.
Trixie ships Node.js 20 via apt — irrelevant since we use nvm exclusively.


5. PM2 Installation

5.1 Install PM2 for both users

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'

Never sudo npm install -g pm2 — installs into system npm, causing PATH failures at boot.

Verify:

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

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)

6.1 Install

su - jss -c 'source /home/jss/.nvm/nvm.sh && npm install -g javascript-solid-server'

Verify:

su - jss -c 'source /home/jss/.nvm/nvm.sh && jss --help'

6.2 Create data directory

mkdir -p /var/lib/jss/data
chown -R jss:jss /var/lib/jss

6.3 Run jss init (interactive sanity check)

sudo -u jss bash -c '
  source /home/jss/.nvm/nvm.sh
  cd /var/lib/jss
  jss init
'
# Walk the prompts to confirm the binary works. Output not used directly.

6.4 Production config file

mkdir -p /etc/jss

Create /etc/jss/config.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"
}

Config key reference (crosschecked against official JSS docs):

Key Ref Value Notes
port 3000 JSS default; Nginx proxies externally
host "127.0.0.1" Loopback only — override from 0.0.0.0
root /var/lib/jss/data Persistent data dir
subdomains true Pod at alice.solidweb.app, not /alice/
baseDomain "solidweb.app" Required for subdomain URI construction
conneg true Turtle ↔ JSON-LD content negotiation
notifications true WebSocket updates (solid-0.1 protocol)
idp true Built-in Identity Provider
idpIssuer ⚠️ gh-pages only "https://solidweb.app" Not in canonical config table; in extended docs. No trailing slash
mashlibCdn true SolidOS browser from unpkg CDN
defaultQuota "1GB" Per-pod storage limit

Open registration: inviteOnly key is absent → registration is fully open.

chown -R jss:jss /etc/jss

6.5 Sanity test before PM2

sudo -u jss bash -c 'source /home/jss/.nvm/nvm.sh && jss start --config /etc/jss/config.json'
# Look for: "Server listening on 127.0.0.1:3000" — then Ctrl+C

7. Uptime Kuma

7.1 Install

su - kuma -c 'source /home/kuma/.nvm/nvm.sh && npm install -g uptime-kuma'

7.2 Create data directory

mkdir -p /var/lib/kuma
chown -R kuma:kuma /var/lib/kuma

7.3 Sanity test before PM2

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'
# Look for: "Server started on port 3001" — then Ctrl+C

No default password. Admin account is created on first browser visit.


8. PM2 Ecosystem Files & Boot Hook

Read fully before executing. Order matters.

8.1 Ecosystem file for JSS

Create /etc/jss/ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: 'jss',
      // Full absolute path — PM2 at boot does not source .bashrc and cannot
      // resolve nvm shims.
      script: '/home/jss/.nvm/versions/node/v24.11.0/bin/jss',
      args: 'start --config /etc/jss/config.json',
      cwd: '/var/lib/jss',

      exec_mode: 'fork',   // correct for JSS — cluster mode is for stateless HTTP apps
      instances: 1,

      autorestart: true,
      watch: false,
      max_restarts: 10,
      min_uptime: '5s',
      restart_delay: 4000,
      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,
      },
    },
  ],
};
chown jss:jss /etc/jss/ecosystem.config.js

8.2 Ecosystem file for Uptime Kuma

Create /home/kuma/ecosystem.config.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,
      },
    },
  ],
};
chown kuma:kuma /home/kuma/ecosystem.config.js

8.3 Start both apps

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
'

8.4 Register PM2 startup hooks (mandatory two-step)

PM2 generates a systemd unit with the exact PATH including the nvm bin directory.
Run pm2 startup as the service user — it prints a sudo env PATH=... command.
Copy-paste that exact output and run it as root.
Running pm2 startup directly as
root, or ignoring the printed command, produces a broken PATH at boot.

For jss:

# Step 1 — run as jss, prints the 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'

# Step 2 — copy and run the EXACT printed command as root, e.g.:
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 kuma:

# 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 and run the EXACT printed command as root, e.g.:
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 process lists (mandatory)

pm2 startup registers the boot hook. pm2 save writes the dump file of processes
to resurrect. Both steps are required — missing pm2 save means nothing restarts.

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 generated systemd units

systemctl status pm2-jss.service
systemctl status pm2-kuma.service
systemctl cat pm2-jss.service
systemctl cat pm2-kuma.service

Both should be active (running) with WantedBy=multi-user.target.


9. Nginx HTTP Scaffolding

9.1 Remove default site

rm -f /etc/nginx/sites-enabled/default
mkdir -p /var/www/certbot

9.2 Temporary HTTP catch-all vhost

Create /etc/nginx/sites-available/solidweb.app:

server {
    listen 80;
    listen [::]:80;
    server_name solidweb.app *.solidweb.app;
    return 301 https://$host$request_uri;
}
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?

*.solidweb.app wildcards cannot be issued via HTTP-01.
Let's Encrypt mandates DNS-01 for all wildcard SANs.

One cert, all subdomains

SANs Stored at
solidweb.app + *.solidweb.app /etc/letsencrypt/live/solidweb.app/

10.1 Request (manual)

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 two TXT records at your registrar

Certbot pauses twice — once per SAN. Both must coexist.

Name Type Value
_acme-challenge.solidweb.app TXT <first token>
_acme-challenge.solidweb.app TXT <second token>

Do not delete the first before adding the second.

10.3 Verify propagation before pressing Enter

dig TXT _acme-challenge.solidweb.app +short
# Both tokens must appear

10.4 Auto-renewal via DNS plugin

On Trixie, python3-certbot-dns-cloudflare 4.0.0 is available in the standard repos.

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

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:

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:

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;

    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)

htpasswd -c /etc/nginx/.htpasswd admin

Create /etc/nginx/sites-available/monitor.solidweb.app:

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:

server {
    listen 80;
    listen [::]:80;
    server_name solidweb.app *.solidweb.app;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # Nginx resolves exact server_name matches before wildcards.
    # status.* and monitor.* are caught by their own blocks above.
    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: solid-0.1 notifications
    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 later)
    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;
        # Required for correct IDP issuer URL in subdomain mode
        proxy_set_header   X-Forwarded-Host  $host;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

11.5 Enable all sites and reload

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 already linked in step 9

nginx -t && systemctl reload nginx

12. Netdata

⚠️ Trixie-specific: Netdata is not in the Debian 13 Trixie official repositories.
Debian removed it because the project's web UI became closed-source software.
apt install netdata will fail with "package not found" on Trixie.

The correct and supported install path on Trixie is Netdata's own kickstart.sh script,
which installs from repository.netdata.cloud. This repository does support Trixie.
Do not try to manually point apt at the Bookworm Netdata repo — use kickstart.sh.

Netdata is not a Node.js process — PM2 is not involved. It runs under its own native
systemd service.

12.1 Install via kickstart.sh

wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh
sh /tmp/netdata-kickstart.sh --dont-start-it --stable-channel

The kickstart script auto-detects Debian 13 Trixie and configures Netdata's own
APT repository. If prompted to claim the agent to Netdata Cloud, you can decline —
the local dashboard at http://127.0.0.1:19999 works fully without cloud registration.

12.2 Bind to localhost only

Edit /etc/netdata/netdata.conf:

[web]
    bind to = 127.0.0.1:19999

12.3 Start and enable

systemctl enable --now netdata
systemctl status netdata

12.4 Verify

curl -s http://127.0.0.1:19999/api/v1/info | python3 -m json.tool | head -20

13. Firewall Rules

ufw allow OpenSSH        # always first
ufw allow 'Nginx Full'   # ports 80 + 443
ufw --force enable
ufw status verbose

Ports 3000, 3001, 19999 remain closed — 127.0.0.1 only via Nginx.


14. Nginx Virtual Host Summary

Incoming request Nginx match Backend 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

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 + subdomain mode

curl -I https://solidweb.app
# Expected: HTTP/2 200

curl -I https://alice.solidweb.app/
# 200 or 401 — both confirm routing works

# Confirm subdomain mode: podUri must be at the subdomain
# With --idp enabled, POST /.pods requires email + password
curl -s -X POST https://solidweb.app/.pods \
  -H "Content-Type: application/json" \
  -d '{"name":"testpod","email":"test@example.com","password":"changeme123"}' \
  | python3 -m json.tool
# "podUri": "https://testpod.solidweb.app/"

# WebSocket notifications header
curl -I https://testpod.solidweb.app/public/
# Updates-Via: wss://testpod.solidweb.app/.notifications

Expected pod structure on disk (per official JSS docs):

/var/lib/jss/data/testpod/
├── index.html          ← WebID profile (HTML + JSON-LD)
├── .acl                ← Root WAC access control
├── inbox/              ← LDP inbox (public append)
│   └── .acl
├── public/
├── private/
│   └── .acl
└── settings/
    ├── prefs
    ├── publicTypeIndex
    └── privateTypeIndex

Wildcard certificate

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

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

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: solidweb.app, alice.solidweb.app, status.solidweb.app, monitor.solidweb.app, SSL cert for solidweb.app (alert 14 days before expiry)
  5. Create a public Status Page

Netdata

curl -s -u admin:yourpassword https://monitor.solidweb.app/api/v1/info | head -5

16. Maintenance & Useful Commands

PM2 daily operations

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'

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'

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'

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'

Update JSS

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

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

wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh
sh /tmp/netdata-kickstart.sh --stable-channel

The kickstart.sh script is also the update mechanism for kickstart-installed Netdata.

Upgrade Node.js version

PM2 documentation: re-run pm2 startup after every Node version change — the binary
path changes with every new nvm-managed version.

NEW=24.12.0

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; 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'

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'

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 PATH in ecosystem files
sed -i "s|v24\.11\.0|v${NEW}|g" /etc/jss/ecosystem.config.js
sed -i "s|v24\.11\.0|v${NEW}|g" /home/kuma/ecosystem.config.js

Update PM2 itself

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
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

Manage storage quotas

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

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 — 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.


Reference documents used:


v6 — solidweb.app · 92.205.60.157 · Debian 13 Trixie (13.4) · Node.js 24.11.0 via nvm · PM2 · April 2026

this document: https://hackmd.io/4faoeQ_USYKMXjreAd3Ldg?view