solidweb.app setup v6
solidweb.app — Full Server Setup Guide (v6)
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
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.
- Netdata uses its own native systemd service (not a Node.js process, not PM2).
- Nginx uses 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.
- Access control model: WAC (Web Access Control,
.acl files) — the JSS default.
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:
3. Server Preparation
Trixie package versions confirmed:
nginx → 1.26.3 · certbot → 4.0 · gcc → 14.2
ufw is not pre-installed on any Debian release — apt install ufw is always needed.
python3-certbot-nginx is intentionally not installed (wildcard = DNS-01 only).
apache2-utils provides htpasswd for Netdata basic auth.
4. Node.js via nvm
4.1 Create dedicated service users
4.2 Install nvm for both users
4.3 Install Node.js 24.11.0
Verify:
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
Never sudo npm install -g pm2 — installs into system npm, causing PATH failures at boot.
Verify:
5.2 Install pm2-logrotate
6. JavaScript Solid Server (JSS)
6.1 Install
Verify:
6.2 Create data directory
6.3 Run jss init (interactive sanity check)
6.4 Production config file
Create /etc/jss/config.json:
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.
6.5 Sanity test before PM2
7. Uptime Kuma
7.1 Install
7.2 Create data directory
7.3 Sanity test before PM2
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:
8.2 Ecosystem file for Uptime Kuma
Create /home/kuma/ecosystem.config.js:
8.3 Start both apps
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:
For 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.
8.6 Verify generated systemd units
Both should be active (running) with WantedBy=multi-user.target.
9. Nginx HTTP Scaffolding
9.1 Remove default site
9.2 Temporary HTTP catch-all vhost
Create /etc/nginx/sites-available/solidweb.app:
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)
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
10.4 Auto-renewal via DNS plugin
On Trixie, python3-certbot-dns-cloudflare 4.0.0 is available in the standard repos.
Other DNS provider plugins: https://certbot.eff.org/docs/using.html#dns-plugins
10.5 Nginx reload hook on renewal
11. Nginx HTTPS Final Config
11.1 Shared TLS snippet
Create /etc/nginx/snippets/ssl-params.conf:
11.2 status.solidweb.app (Uptime Kuma)
Create /etc/nginx/sites-available/status.solidweb.app:
11.3 monitor.solidweb.app (Netdata)
Create /etc/nginx/sites-available/monitor.solidweb.app:
11.4 solidweb.app + all pod subdomains (JSS)
Overwrite /etc/nginx/sites-available/solidweb.app:
11.5 Enable all sites and reload
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.
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:
12.3 Start and enable
12.4 Verify
13. Firewall Rules
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
JSS — open registration + subdomain mode
Expected pod structure on disk (per official JSS docs):
Wildcard certificate
Uptime Kuma
- Open
https://status.solidweb.app
- Create admin account (no default password — first-run wizard)
- Enable 2FA in Settings → Security
- Add monitors:
solidweb.app, alice.solidweb.app, status.solidweb.app, monitor.solidweb.app, SSL cert for solidweb.app (alert 14 days before expiry)
- Create a public Status Page
Netdata
16. Maintenance & Useful Commands
PM2 daily operations
Update JSS
Update Uptime Kuma
Update Netdata
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.
Update PM2 itself
Manage storage quotas
Manual certificate renewal
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
solidweb.app setup v6
solidweb.app — Full Server Setup Guide (v6)
tags:
melvinmatthiassolidjssdebiantrixiepm2nginxletsencryptServer:
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.appStack: 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
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.
apt install nginxunchangedpython3-certbot-dns-cloudflareapt installunchangedapt install ufwapt install ufw1. Architecture Overview
Process management strategy:
jss,kuma).startupcommand auto-generates a systemd unit for each user.127.0.0.1only; Nginx is the sole public gateway.<username>.solidweb.app..aclfiles) — the JSS default.2. DNS Setup
solidweb.app92.205.60.157*.solidweb.app92.205.60.157Verify 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.1573. Server Preparation
4. Node.js via nvm
4.1 Create dedicated service users
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.05. 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'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/jss6.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/jssCreate
/etc/jss/config.json:Config key reference (crosschecked against official JSS docs):
port3000host"127.0.0.1"0.0.0.0root/var/lib/jss/datasubdomainstruealice.solidweb.app, not/alice/baseDomain"solidweb.app"connegtruenotificationstrueidptrueidpIssuer"https://solidweb.app"mashlibCdntruedefaultQuota"1GB"chown -R jss:jss /etc/jss6.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+C7. 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/kuma7.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+C8. 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.js8.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.js8.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
PATHincluding the nvm bin directory.Run
pm2 startupas the service user — it prints asudo env PATH=...command.Copy-paste that exact output and run it as root. Running
pm2 startupdirectly asroot, 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-jssFor
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-kuma8.5 Save process lists (mandatory)
pm2 startupregisters the boot hook.pm2 savewrites the dump file of processesto resurrect. Both steps are required — missing
pm2 savemeans 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.serviceBoth should be
active (running)withWantedBy=multi-user.target.9. Nginx HTTP Scaffolding
9.1 Remove default site
rm -f /etc/nginx/sites-enabled/default mkdir -p /var/www/certbot9.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 nginx10. Let's Encrypt Wildcard Certificate (DNS-01)
Why DNS-01?
*.solidweb.appwildcards cannot be issued via HTTP-01.Let's Encrypt mandates DNS-01 for all wildcard SANs.
One cert, all subdomains
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.
_acme-challenge.solidweb.app<first token>_acme-challenge.solidweb.app<second token>10.3 Verify propagation before pressing Enter
dig TXT _acme-challenge.solidweb.app +short # Both tokens must appear10.4 Auto-renewal via DNS plugin
On Trixie,
python3-certbot-dns-cloudflare4.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'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-run11. 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)
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 nginx12. Netdata
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
12.2 Bind to localhost only
Edit
/etc/netdata/netdata.conf:[web] bind to = 127.0.0.1:1999912.3 Start and enable
systemctl enable --now netdata systemctl status netdata12.4 Verify
curl -s http://127.0.0.1:19999/api/v1/info | python3 -m json.tool | head -2013. Firewall Rules
ufw allow OpenSSH # always first ufw allow 'Nginx Full' # ports 80 + 443 ufw --force enable ufw status verbose14. Nginx Virtual Host Summary
https://solidweb.appsolidweb.app:3000https://alice.solidweb.app*.solidweb.app:3000https://status.solidweb.app:3001https://monitor.solidweb.app:19999http://*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.serviceJSS — 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/.notificationsExpected pod structure on disk (per official JSS docs):
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 doneUptime Kuma
https://status.solidweb.appsolidweb.app,alice.solidweb.app,status.solidweb.app,monitor.solidweb.app, SSL cert forsolidweb.app(alert 14 days before expiry)Netdata
curl -s -u admin:yourpassword https://monitor.solidweb.app/api/v1/info | head -516. 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
Upgrade Node.js version
PM2 documentation: re-run
pm2 startupafter every Node version change — the binarypath 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.jsUpdate 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 eachManage 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
Summary: Port & Service Map
pm2-jss)jsshttps://solidweb.app+https://*.solidweb.apppm2-kuma)kumahttps://status.solidweb.apphttps://monitor.solidweb.appCredits
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 keyfigure 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