Weitere Palaver-Server auf einem Host
- Hauptserver plus beliebig viele weitere Server auf einem Host
- mit dem Hauptserver geteilt: Programme (
/opt/palaver/bin), Web-Client (/opt/palaver/web), LiveKit - Updates: Update-Anleitung für mehrere Server
Für jeden weiteren Server (Beispiel: guild):
| Bestandteil | Wert |
|---|---|
| Name | guild |
| Domain | guild.palaver.social |
| Systembenutzer, Datenbankrolle | palaver-guild |
| Datenbank | palaver_guild |
| Konfiguration | /opt/palaver/instances/guild/palaverd.env |
| systemd-Unit | palaverd@guild |
| Port | 8091 (Schritt 3) |
| LiveKit-Raumpräfix | guild- (PALAVER_LIVEKIT_ROOM_PREFIX) |
| LiveKit-Schlüssel | eigener Schlüssel in /opt/palaver/etc/livekit.yaml |
| nginx | Site palaver-guild, Snippet palaver-web-guild.conf |
| nginx-Logs | /var/log/nginx/palaver/guild.access.log, guild.error.log |
Platzhalter in den Befehlen:
| Platzhalter | Ersetzen durch |
|---|---|
guild |
den Namen des Servers (Schritt 2), in jedem Namen der Tabelle oben; ein - im Namen wird im Datenbanknamen zu _ (z. B. palaver_my_guild) |
guild.palaver.social |
die Domain des Servers |
8091 |
den Port des Servers (Schritt 3) |
palaver.social |
in /etc/letsencrypt/live/palaver.social/: den Namen des Zertifikats (eigenes Zertifikat: die Domain des Servers); sonst: die Domain des Hauptservers |
you@example.org |
E-Mail-Adresse für Let's Encrypt; nur eigenes Zertifikat |
<livekit-key>, <livekit-secret> |
Ausgabe aus Schritt 7, „Erzeuge … für die LiveKit-API“ |
'Guild', 'Guild subtitle' |
Name und Untertitel des Servers in der App (Schritt 12); kein Untertitel: '' |
2026-10-10 |
das heutige Datum (Namen der Sicherungsdateien) |
vi |
Editor deiner Wahl |
1. Voraussetzungen
- Hauptserver nach Palavers
INSTALL.md(palaverd.service, Benutzer und Datenbankpalaver,/opt/palaver/etc, LiveKit, Snippetpalaver-web.conf), Port 8090 - Shell-Zugang mit
sudo - keine weiteren Ports: 443 und die LiveKit-Ports des Hauptservers
Prüfe, ob der Hauptserver da ist.
sudo ls /opt/palaver/etc/palaverd.env /opt/palaver/etc/livekit.yaml
2. Name und Domain
- Name:
a-z,0-9,-(nicht als erstes oder letztes Zeichen), höchstens 24 Zeichen - reserviert:
palaver,www,account,konto,support,api,admin,dogan,livekit,mail,status - Domain: Hostname, kleingeschrieben
- Zertifikat: das Wildcard-Zertifikat
palaver.socialoder ein eigenes (Schritt 9) - Name und Untertitel in der App: je bis zu 64 Zeichen
- eine Bestellung über account.palaver.social zeigt diese Werte zum Kopieren
Prüfe, ob der Name frei ist; erwartet: beide Befehle schlagen fehl.
ls -d /opt/palaver/instances/guild
id palaver-guild
3. Port
- ein localhost-Port je Server; Hauptserver: 8090
- neuer Server: höchster belegter Port + 1
Zeige die belegten Ports; nimm den höchsten + 1.
sudo sh -c 'grep -H "^PALAVER_LISTEN=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env 2>/dev/null'
4. Unit-Vorlage
Einmal je Host; überspringen, wenn /etc/systemd/system/palaverd@.service existiert.
ls /etc/systemd/system/palaverd@.service
Öffne /etc/systemd/system/palaverd@.service in einem Editor.
sudo vi /etc/systemd/system/palaverd@.service
Inhalt, unverändert:
[Unit]
Description=Palaver app server %i
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
User=palaver-%i
Group=palaver-%i
EnvironmentFile=/opt/palaver/instances/%i/palaverd.env
ExecStart=/opt/palaver/bin/palaverd serve
Restart=on-failure
RestartSec=2
LogNamespace=palaver
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
Lade sie.
sudo systemctl daemon-reload
5. Benutzer und Verzeichnis
Lege das Verzeichnis für alle weiteren Server an.
sudo install -d -o root -g root -m 755 /opt/palaver/instances
Lege den Systembenutzer an.
sudo useradd --system --home-dir /opt/palaver/instances/guild --no-create-home --shell /usr/sbin/nologin palaver-guild
Lege das Konfigurationsverzeichnis an.
sudo install -d -o palaver-guild -g palaver-guild -m 700 /opt/palaver/instances/guild
6. PostgreSQL
Lege die Datenbankrolle und die Datenbank an.
sudo -u postgres createuser palaver-guild
sudo -u postgres createdb -O palaver-guild palaver_guild
7. LiveKit-Schlüssel
Erzeuge den Schlüssel für die LiveKit-API (<livekit-key>).
openssl rand -hex 8 | sed 's/^/API/'
Erzeuge das Geheimnis für die LiveKit-API (<livekit-secret>).
openssl rand -base64 36 | tr -d '/+='
Öffne /opt/palaver/etc/livekit.yaml in einem Editor.
sudo vi /opt/palaver/etc/livekit.yaml
Füge unter keys: eine Zeile hinzu, um zwei Leerzeichen eingerückt:
<livekit-key>: <livekit-secret>
Prüfe die Schlüssel; erwartet: eine Zeile je Server.
sudo sed -n '/^keys:/,/^[a-z]/p' /opt/palaver/etc/livekit.yaml
Starte LiveKit neu; Gespräche auf allen Servern brechen ab.
sudo systemctl restart livekit
- LiveKit-Schlüssel sind nicht auf Räume begrenzt: die Konfiguration eines Servers gibt Zugriff auf die Gespräche aller Server
8. palaverd
Öffne /opt/palaver/instances/guild/palaverd.env in einem Editor.
sudo vi /opt/palaver/instances/guild/palaverd.env
Inhalt:
PALAVER_DATABASE_URL=postgres:///palaver_guild?host=/var/run/postgresql
PALAVER_LISTEN=127.0.0.1:8091
PALAVER_PUBLIC_URL=https://guild.palaver.social
PALAVER_LIVEKIT_URL=wss://guild.palaver.social/livekit
PALAVER_LIVEKIT_API_URL=http://127.0.0.1:7880
PALAVER_LIVEKIT_API_KEY=<livekit-key>
PALAVER_LIVEKIT_API_SECRET=<livekit-secret>
PALAVER_LIVEKIT_ROOM_PREFIX=guild-
PALAVER_OPEN_REGISTRATION=false
PALAVER_CTL="sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env"
PALAVER_UNIT=palaverd@guild
PALAVER_ENV_FILE=/opt/palaver/instances/guild/palaverd.env
Mach sie nur für palaver-guild lesbar.
sudo chown palaver-guild:palaver-guild /opt/palaver/instances/guild/palaverd.env
sudo chmod 600 /opt/palaver/instances/guild/palaverd.env
- weitere Einstellungen: Palavers
INSTALL.md, Konfigurationsreferenz
9. Zertifikat
Unter einem Wildcard-Zertifikat
Prüfe, ob es die Domain abdeckt; erwartet: DNS:*.palaver.social.
sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/palaver.social/fullchain.pem
Weiter: Schritt 10.
Eigenes Zertifikat
Öffne /etc/nginx/sites-available/palaver-guild in einem Editor.
sudo vi /etc/nginx/sites-available/palaver-guild
Inhalt:
server {
listen 80;
listen [::]:80;
server_name guild.palaver.social;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 404; }
}
Aktiviere die Site, prüfe und lade sie.
sudo ln -sf /etc/nginx/sites-available/palaver-guild /etc/nginx/sites-enabled/palaver-guild
sudo nginx -t && sudo systemctl reload nginx
Hole das Zertifikat.
sudo certbot certonly --webroot -w /var/www/html -d guild.palaver.social -m you@example.org \
--agree-tos --no-eff-email --non-interactive \
--deploy-hook "systemctl reload nginx"
- Site in Schritt 10:
/etc/letsencrypt/live/guild.palaver.social/statt/etc/letsencrypt/live/palaver.social/
10. nginx
Öffne /etc/nginx/snippets/palaver-web-guild.conf in einem Editor.
sudo vi /etc/nginx/snippets/palaver-web-guild.conf
Inhalt:
root /opt/palaver/web;
index index.html;
location / {
try_files $uri /index.html;
add_header Cache-Control "no-cache" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://guild.palaver.social; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), geolocation=(), payment=()" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
location /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://guild.palaver.social; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
Öffne /etc/nginx/sites-available/palaver-guild in einem Editor.
sudo vi /etc/nginx/sites-available/palaver-guild
Inhalt:
server {
listen 80;
listen [::]:80;
server_name guild.palaver.social;
access_log /var/log/nginx/palaver/guild.access.log;
error_log /var/log/nginx/palaver/guild.error.log;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name guild.palaver.social;
ssl_certificate /etc/letsencrypt/live/palaver.social/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/palaver.social/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
access_log /var/log/nginx/palaver/guild.access.log;
error_log /var/log/nginx/palaver/guild.error.log;
client_max_body_size 64k;
# server push, one WebSocket per client
location = /api/events {
proxy_pass http://127.0.0.1:8091;
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 1h;
proxy_send_timeout 1h;
}
# profile pictures, server icon: 5 MiB
location ~ ^/api/(me/avatar|server/icon)$ {
client_max_body_size 5m;
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# attachments: 25 MiB
location ~ ^/api/channels/[0-9]+/attachments$ {
client_max_body_size 25m;
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# LiveKit signalling, shared; the trailing slash strips /livekit
location /livekit/ {
proxy_pass http://127.0.0.1:7880/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
include snippets/palaver-web-guild.conf;
}
Aktiviere, prüfe und lade sie.
sudo ln -sf /etc/nginx/sites-available/palaver-guild /etc/nginx/sites-enabled/palaver-guild
sudo nginx -t && sudo systemctl reload nginx
11. Starten
Starte den Server.
sudo systemctl enable --now palaverd@guild
Prüfe, ob er antwortet; erwartet: JSON.
curl -fsS http://127.0.0.1:8091/api/registration
12. Erster Admin
Setze Name und Untertitel des Servers.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env profile -name 'Guild' -subtitle 'Guild subtitle'
Erzeuge einen Einladungscode für den ersten Admin; einmal nutzbar, 7 Tage gültig.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env invite -role Admin -valid 168h
- registrieren: Palaver-App → Server
https://guild.palaver.social→ Registrieren → Code - erster Admin = Eigentümer des Servers
- Konto ohne Admin registriert (Code ohne
-role Admin): Rolle nachträglich vergeben
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env grant <username> Admin
13. Prüfen
Führe doctor aus; erwartet: ok in jeder Zeile.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env doctor
Prüfe, ob der Hauptserver weiter antwortet.
sudo -u palaver /opt/palaver/bin/palaverd -env /opt/palaver/etc/palaverd.env doctor
Server auflisten
Namen der weiteren Server.
ls /opt/palaver/instances/
Domain und Port jedes Servers.
sudo sh -c 'grep -H -E "^PALAVER_(PUBLIC_URL|LISTEN)=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env'
Name und Untertitel eines Servers.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env profile
Zustand jedes Servers.
systemctl list-units --all 'palaverd*'
Sicherung
- Datenbank: alle Daten des Servers
/opt/palaver/instances/guild: Konfiguration, LiveKit-Schlüssel
Sichere die Datenbank.
sudo -u palaver-guild pg_dump -Fc palaver_guild > palaver-guild-2026-10-10.dump
Sichere die Konfiguration.
sudo tar -czf palaver-guild-etc-2026-10-10.tar.gz -C /opt/palaver/instances guild
Wiederherstellen
Voraussetzung: Server mit demselben Namen und derselben Domain, Schritte 1–12.
Halte ihn an.
sudo systemctl stop palaverd@guild
Destruktiv: Lösche die Datenbank des neuen Servers (palaver_guild, alle seine Konten und Nachrichten).
sudo -u postgres dropdb palaver_guild
Lege sie neu an und lade die Sicherung.
sudo -u postgres createdb -O palaver-guild palaver_guild
sudo -u palaver-guild pg_restore -d palaver_guild palaver-guild-2026-10-10.dump
Starte ihn.
sudo systemctl start palaverd@guild
Server entfernen
Zeige die Domain und den LiveKit-Schlüssel (<livekit-key>).
sudo grep -E '^PALAVER_(PUBLIC_URL|LIVEKIT_API_KEY)=' /opt/palaver/instances/guild/palaverd.env
Zeige das Zertifikat der Site; eigenes Zertifikat: Name guild.palaver.social.
grep ssl_certificate /etc/nginx/sites-available/palaver-guild
Vorher sichern: Sicherung.
Halte den Dienst an und deaktiviere ihn.
sudo systemctl disable --now palaverd@guild
Entferne nginx-Site und -Snippet und lade nginx neu.
sudo rm /etc/nginx/sites-enabled/palaver-guild /etc/nginx/sites-available/palaver-guild /etc/nginx/snippets/palaver-web-guild.conf
sudo nginx -t && sudo systemctl reload nginx
Destruktiv: Lösche die Datenbank palaver_guild (alle Konten, Nachrichten, Anhänge) und ihre Rolle palaver-guild.
sudo -u postgres dropdb palaver_guild
sudo -u postgres dropuser palaver-guild
Destruktiv: Lösche den LiveKit-Schlüssel des Servers; öffne /opt/palaver/etc/livekit.yaml in einem Editor.
sudo vi /opt/palaver/etc/livekit.yaml
Entferne:
- unter
keys:die Zeile<livekit-key>: …
Starte LiveKit neu; Gespräche auf den anderen Servern brechen kurz ab.
sudo systemctl restart livekit
Destruktiv: Lösche die Konfiguration /opt/palaver/instances/guild und den Benutzer palaver-guild.
sudo rm -r /opt/palaver/instances/guild
sudo userdel palaver-guild
Destruktiv: Lösche das Zertifikat; nur ein eigenes (guild.palaver.social), nie das Wildcard-Zertifikat.
sudo certbot delete --cert-name guild.palaver.social
Destruktiv: Lösche die nginx-Logs des Servers (/var/log/nginx/palaver/guild.*).
sudo sh -c 'rm /var/log/nginx/palaver/guild.access.log* /var/log/nginx/palaver/guild.error.log*'
Nach dem letzten weiteren Server
Entferne die Unit-Vorlage.
sudo rm /etc/systemd/system/palaverd@.service
sudo systemctl daemon-reload
Entferne das leere Verzeichnis.
sudo rmdir /opt/palaver/instances
- vor dem Deinstallieren des Hauptservers (Palavers
INSTALL.md, Deinstallieren): alle weiteren Server entfernen
Optional: unbekannte Namen zum Hauptserver
- für: Wildcard-DNS-Eintrag und -Zertifikat für die Domain des Hauptservers
- unbekannte Namen (Tippfehler): Umleitung zum Hauptserver statt zu nginx' erster Site
Prüfe, ob das Zertifikat *.palaver.social abdeckt.
sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/palaver.social/fullchain.pem
Prüfe, ob die Platzhalter-Site der Distribution default_server beansprucht.
grep default_server /etc/nginx/sites-enabled/default
Falls ja: deaktiviere sie.
sudo rm /etc/nginx/sites-enabled/default
Öffne /etc/nginx/sites-available/palaver-catch-all in einem Editor.
sudo vi /etc/nginx/sites-available/palaver-catch-all
Inhalt:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://palaver.social/; }
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/palaver.social/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/palaver.social/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://palaver.social/;
}
Aktiviere, prüfe und lade sie.
sudo ln -sf /etc/nginx/sites-available/palaver-catch-all /etc/nginx/sites-enabled/palaver-catch-all
sudo nginx -t && sudo systemctl reload nginx
Optional: Wildcard-Zertifikat
- ein Zertifikat für die Hauptdomain und jeden Namen darunter (
palaver.social,*.palaver.social) - certbot-DNS-Prüfung
- palaver.social: certbot-Snap, Plugin
certbot-dns-hetzner-cloud, Zugangsdaten/root/.secrets/hetzner.ini, Deploy-Hooksystemctl reload nginx - nur ein certbot je Host
Liste die Zertifikate und die Namen auf, die sie abdecken.
sudo certbot certificates
Prüfe den Deploy-Hook des Wildcard-Zertifikats.
sudo grep -E 'authenticator|renew_hook|deploy_hook' /etc/letsencrypt/renewal/palaver.social.conf
Dateien
| Pfad | Inhalt |
|---|---|
/etc/systemd/system/palaverd@.service |
Unit-Vorlage |
/opt/palaver/instances/guild/palaverd.env |
Konfiguration (nur Benutzer palaver-guild) |
/opt/palaver/etc/livekit.yaml |
ein Schlüssel je Server unter keys: |
/etc/nginx/sites-available/palaver-guild |
nginx-Site |
/etc/nginx/snippets/palaver-web-guild.conf |
Web-Client, CSP für guild.palaver.social |
/etc/nginx/sites-available/palaver-catch-all |
unbekannte Namen (optional) |
/var/log/nginx/palaver/guild.access.log, guild.error.log |
nginx-Logs (rotiert von Palavers /etc/logrotate.d/palaver) |
Fehlersuche
doctor eines Servers.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env doctor
Was palaverd meldet.
sudo journalctl --namespace=palaver -u palaverd@guild -n 100
Was nginx meldet.
sudo tail -n 50 /var/log/nginx/palaver/guild.error.log
| Symptom | Ursache |
|---|---|
useradd: invalid user name |
Name zu lang (höchstens 24 Zeichen) |
| Sprache geht nur auf diesem Server nicht | Schlüssel fehlt in livekit.yaml oder LiveKit nach Schritt 7 nicht neu gestartet |
| Sprache geht auf keinem Server | livekit.yaml durch eine Änderung kaputt: sudo journalctl --namespace=palaver -u livekit -n 50 |
| die Site eines anderen Servers antwortet | server_name oder der Symlink in sites-enabled fehlt |
| Web-Client im Browser ohne Sprache | CSP: wss://guild.palaver.social fehlt in palaver-web-guild.conf |
Further Palaver servers on one host
- main server plus any number of further servers on one host
- shared with the main server: programs (
/opt/palaver/bin), web client (/opt/palaver/web), LiveKit - updates: the update guide for several servers
For each further server (example: guild):
| Part | Value |
|---|---|
| name | guild |
| domain | guild.palaver.social |
| system user, database role | palaver-guild |
| database | palaver_guild |
| configuration | /opt/palaver/instances/guild/palaverd.env |
| systemd unit | palaverd@guild |
| port | 8091 (step 3) |
| LiveKit room prefix | guild- (PALAVER_LIVEKIT_ROOM_PREFIX) |
| LiveKit key | own key in /opt/palaver/etc/livekit.yaml |
| nginx | site palaver-guild, snippet palaver-web-guild.conf |
| nginx logs | /var/log/nginx/palaver/guild.access.log, guild.error.log |
Placeholders in the commands:
| Placeholder | Replace with |
|---|---|
guild |
the server's name (step 2), in every name of the table above; a - in the name becomes _ in the database name (e.g. palaver_my_guild) |
guild.palaver.social |
the server's domain |
8091 |
the server's port (step 3) |
palaver.social |
in /etc/letsencrypt/live/palaver.social/: the certificate's name (own certificate: the server's domain); elsewhere: the main server's domain |
you@example.org |
e-mail address for Let's Encrypt; own certificate only |
<livekit-key>, <livekit-secret> |
output of step 7, "Generate the LiveKit API …" |
'Guild', 'Guild subtitle' |
the server's name and subtitle in the app (step 12); no subtitle: '' |
2026-10-10 |
today's date (backup file names) |
vi |
editor of your choice |
1. Requirements
- main server per Palaver's
INSTALL.md(palaverd.service, user and databasepalaver,/opt/palaver/etc, LiveKit, snippetpalaver-web.conf), port 8090 - shell access with
sudo - no further ports: 443 and the main server's LiveKit ports
Check that the main server is there.
sudo ls /opt/palaver/etc/palaverd.env /opt/palaver/etc/livekit.yaml
2. Name and domain
- name:
a-z,0-9,-(not first or last), at most 24 characters - reserved:
palaver,www,account,konto,support,api,admin,dogan,livekit,mail,status - domain: host name, lower case
- certificate: the wildcard certificate
palaver.social, or an own one (step 9) - name and subtitle in the app: up to 64 characters each
- an order from account.palaver.social shows these values ready to copy
Check that the name is free; expected: both commands fail.
ls -d /opt/palaver/instances/guild
id palaver-guild
3. Port
- one localhost port per server; main server: 8090
- new server: highest port in use + 1
Show the ports in use; take the highest + 1.
sudo sh -c 'grep -H "^PALAVER_LISTEN=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env 2>/dev/null'
4. Unit template
Once per host; skip if /etc/systemd/system/palaverd@.service exists.
ls /etc/systemd/system/palaverd@.service
Open /etc/systemd/system/palaverd@.service in an editor.
sudo vi /etc/systemd/system/palaverd@.service
Content, unchanged:
[Unit]
Description=Palaver app server %i
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
User=palaver-%i
Group=palaver-%i
EnvironmentFile=/opt/palaver/instances/%i/palaverd.env
ExecStart=/opt/palaver/bin/palaverd serve
Restart=on-failure
RestartSec=2
LogNamespace=palaver
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
Load it.
sudo systemctl daemon-reload
5. User and directory
Create the directory for all further servers.
sudo install -d -o root -g root -m 755 /opt/palaver/instances
Create the system user.
sudo useradd --system --home-dir /opt/palaver/instances/guild --no-create-home --shell /usr/sbin/nologin palaver-guild
Create the configuration directory.
sudo install -d -o palaver-guild -g palaver-guild -m 700 /opt/palaver/instances/guild
6. PostgreSQL
Create the database role and the database.
sudo -u postgres createuser palaver-guild
sudo -u postgres createdb -O palaver-guild palaver_guild
7. LiveKit key
Generate the LiveKit API key (<livekit-key>).
openssl rand -hex 8 | sed 's/^/API/'
Generate the LiveKit API secret (<livekit-secret>).
openssl rand -base64 36 | tr -d '/+='
Open /opt/palaver/etc/livekit.yaml in an editor.
sudo vi /opt/palaver/etc/livekit.yaml
Add a line under keys:, indented by two spaces:
<livekit-key>: <livekit-secret>
Check the keys; expected: one line per server.
sudo sed -n '/^keys:/,/^[a-z]/p' /opt/palaver/etc/livekit.yaml
Restart LiveKit; calls on every server drop.
sudo systemctl restart livekit
- LiveKit keys are not limited to rooms: one server's configuration gives access to all servers' calls
8. palaverd
Open /opt/palaver/instances/guild/palaverd.env in an editor.
sudo vi /opt/palaver/instances/guild/palaverd.env
Content:
PALAVER_DATABASE_URL=postgres:///palaver_guild?host=/var/run/postgresql
PALAVER_LISTEN=127.0.0.1:8091
PALAVER_PUBLIC_URL=https://guild.palaver.social
PALAVER_LIVEKIT_URL=wss://guild.palaver.social/livekit
PALAVER_LIVEKIT_API_URL=http://127.0.0.1:7880
PALAVER_LIVEKIT_API_KEY=<livekit-key>
PALAVER_LIVEKIT_API_SECRET=<livekit-secret>
PALAVER_LIVEKIT_ROOM_PREFIX=guild-
PALAVER_OPEN_REGISTRATION=false
PALAVER_CTL="sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env"
PALAVER_UNIT=palaverd@guild
PALAVER_ENV_FILE=/opt/palaver/instances/guild/palaverd.env
Make it readable by palaver-guild only.
sudo chown palaver-guild:palaver-guild /opt/palaver/instances/guild/palaverd.env
sudo chmod 600 /opt/palaver/instances/guild/palaverd.env
- other settings: Palaver's
INSTALL.md, Configuration reference
9. Certificate
Under a wildcard certificate
Check that it covers the domain; expected: DNS:*.palaver.social.
sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/palaver.social/fullchain.pem
Next: step 10.
Own certificate
Open /etc/nginx/sites-available/palaver-guild in an editor.
sudo vi /etc/nginx/sites-available/palaver-guild
Content:
server {
listen 80;
listen [::]:80;
server_name guild.palaver.social;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 404; }
}
Enable the site, check and load it.
sudo ln -sf /etc/nginx/sites-available/palaver-guild /etc/nginx/sites-enabled/palaver-guild
sudo nginx -t && sudo systemctl reload nginx
Get the certificate.
sudo certbot certonly --webroot -w /var/www/html -d guild.palaver.social -m you@example.org \
--agree-tos --no-eff-email --non-interactive \
--deploy-hook "systemctl reload nginx"
- step 10's site:
/etc/letsencrypt/live/guild.palaver.social/instead of/etc/letsencrypt/live/palaver.social/
10. nginx
Open /etc/nginx/snippets/palaver-web-guild.conf in an editor.
sudo vi /etc/nginx/snippets/palaver-web-guild.conf
Content:
root /opt/palaver/web;
index index.html;
location / {
try_files $uri /index.html;
add_header Cache-Control "no-cache" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://guild.palaver.social; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), geolocation=(), payment=()" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
location /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://guild.palaver.social; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
Open /etc/nginx/sites-available/palaver-guild in an editor.
sudo vi /etc/nginx/sites-available/palaver-guild
Content:
server {
listen 80;
listen [::]:80;
server_name guild.palaver.social;
access_log /var/log/nginx/palaver/guild.access.log;
error_log /var/log/nginx/palaver/guild.error.log;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name guild.palaver.social;
ssl_certificate /etc/letsencrypt/live/palaver.social/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/palaver.social/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
access_log /var/log/nginx/palaver/guild.access.log;
error_log /var/log/nginx/palaver/guild.error.log;
client_max_body_size 64k;
# server push, one WebSocket per client
location = /api/events {
proxy_pass http://127.0.0.1:8091;
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 1h;
proxy_send_timeout 1h;
}
# profile pictures, server icon: 5 MiB
location ~ ^/api/(me/avatar|server/icon)$ {
client_max_body_size 5m;
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# attachments: 25 MiB
location ~ ^/api/channels/[0-9]+/attachments$ {
client_max_body_size 25m;
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# LiveKit signalling, shared; the trailing slash strips /livekit
location /livekit/ {
proxy_pass http://127.0.0.1:7880/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
include snippets/palaver-web-guild.conf;
}
Enable, check and load it.
sudo ln -sf /etc/nginx/sites-available/palaver-guild /etc/nginx/sites-enabled/palaver-guild
sudo nginx -t && sudo systemctl reload nginx
11. Start
Start the server.
sudo systemctl enable --now palaverd@guild
Check that it answers; expected: JSON.
curl -fsS http://127.0.0.1:8091/api/registration
12. First admin
Set the server's name and subtitle.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env profile -name 'Guild' -subtitle 'Guild subtitle'
Create an invite code for the first admin; single use, valid 7 days.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env invite -role Admin -valid 168h
- register: Palaver app → server
https://guild.palaver.social→ Register → code - first admin = server owner
- account registered without admin (code without
-role Admin): give it the role
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env grant <username> Admin
13. Check
Run doctor; expected: ok on every line.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env doctor
Check that the main server still answers.
sudo -u palaver /opt/palaver/bin/palaverd -env /opt/palaver/etc/palaverd.env doctor
List servers
Names of the further servers.
ls /opt/palaver/instances/
Domain and port of every server.
sudo sh -c 'grep -H -E "^PALAVER_(PUBLIC_URL|LISTEN)=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env'
Name and subtitle of one server.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env profile
State of every server.
systemctl list-units --all 'palaverd*'
Backup
- database: all data of the server
/opt/palaver/instances/guild: configuration, LiveKit key
Dump the database.
sudo -u palaver-guild pg_dump -Fc palaver_guild > palaver-guild-2026-10-10.dump
Save the configuration.
sudo tar -czf palaver-guild-etc-2026-10-10.tar.gz -C /opt/palaver/instances guild
Restore
Prerequisite: server with the same name and domain, steps 1–12.
Stop it.
sudo systemctl stop palaverd@guild
Destructive: delete the database of the new server (palaver_guild, all its accounts and messages).
sudo -u postgres dropdb palaver_guild
Create it again and load the dump.
sudo -u postgres createdb -O palaver-guild palaver_guild
sudo -u palaver-guild pg_restore -d palaver_guild palaver-guild-2026-10-10.dump
Start it.
sudo systemctl start palaverd@guild
Remove a server
Show the domain and the LiveKit key (<livekit-key>).
sudo grep -E '^PALAVER_(PUBLIC_URL|LIVEKIT_API_KEY)=' /opt/palaver/instances/guild/palaverd.env
Show the site's certificate; own certificate: name guild.palaver.social.
grep ssl_certificate /etc/nginx/sites-available/palaver-guild
Back up first: Backup.
Stop and disable the service.
sudo systemctl disable --now palaverd@guild
Remove the nginx site and snippet and reload nginx.
sudo rm /etc/nginx/sites-enabled/palaver-guild /etc/nginx/sites-available/palaver-guild /etc/nginx/snippets/palaver-web-guild.conf
sudo nginx -t && sudo systemctl reload nginx
Destructive: delete the database palaver_guild (all accounts, messages, attachments) and its role palaver-guild.
sudo -u postgres dropdb palaver_guild
sudo -u postgres dropuser palaver-guild
Destructive: delete the server's LiveKit key; open /opt/palaver/etc/livekit.yaml in an editor.
sudo vi /opt/palaver/etc/livekit.yaml
Remove:
- under
keys:the line<livekit-key>: …
Restart LiveKit; calls on the other servers drop briefly.
sudo systemctl restart livekit
Destructive: delete the configuration /opt/palaver/instances/guild and the user palaver-guild.
sudo rm -r /opt/palaver/instances/guild
sudo userdel palaver-guild
Destructive: delete the certificate; only an own one (guild.palaver.social), never the wildcard certificate.
sudo certbot delete --cert-name guild.palaver.social
Destructive: delete nginx's logs of the server (/var/log/nginx/palaver/guild.*).
sudo sh -c 'rm /var/log/nginx/palaver/guild.access.log* /var/log/nginx/palaver/guild.error.log*'
After the last further server
Remove the unit template.
sudo rm /etc/systemd/system/palaverd@.service
sudo systemctl daemon-reload
Remove the empty directory.
sudo rmdir /opt/palaver/instances
- before uninstalling the main server (Palaver's
INSTALL.md, Uninstall): remove every further server
Optional: unknown names to the main server
- for: wildcard DNS record and certificate for the main server's domain
- unknown names (typos): redirect to the main server instead of nginx's first site
Check that the certificate covers *.palaver.social.
sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/palaver.social/fullchain.pem
Check whether the distribution's placeholder site claims default_server.
grep default_server /etc/nginx/sites-enabled/default
If so: disable it.
sudo rm /etc/nginx/sites-enabled/default
Open /etc/nginx/sites-available/palaver-catch-all in an editor.
sudo vi /etc/nginx/sites-available/palaver-catch-all
Content:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://palaver.social/; }
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/palaver.social/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/palaver.social/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://palaver.social/;
}
Enable, check and load it.
sudo ln -sf /etc/nginx/sites-available/palaver-catch-all /etc/nginx/sites-enabled/palaver-catch-all
sudo nginx -t && sudo systemctl reload nginx
Optional: wildcard certificate
- one certificate for the main domain and every name under it (
palaver.social,*.palaver.social) - certbot DNS challenge
- palaver.social: certbot snap, plugin
certbot-dns-hetzner-cloud, credentials/root/.secrets/hetzner.ini, deploy hooksystemctl reload nginx - one certbot per host only
List the certificates and the names they cover.
sudo certbot certificates
Check the deploy hook of the wildcard certificate.
sudo grep -E 'authenticator|renew_hook|deploy_hook' /etc/letsencrypt/renewal/palaver.social.conf
Files
| Path | Content |
|---|---|
/etc/systemd/system/palaverd@.service |
unit template |
/opt/palaver/instances/guild/palaverd.env |
configuration (user palaver-guild only) |
/opt/palaver/etc/livekit.yaml |
one key per server under keys: |
/etc/nginx/sites-available/palaver-guild |
nginx site |
/etc/nginx/snippets/palaver-web-guild.conf |
web client, CSP for guild.palaver.social |
/etc/nginx/sites-available/palaver-catch-all |
unknown names (optional) |
/var/log/nginx/palaver/guild.access.log, guild.error.log |
nginx logs (rotated by Palaver's /etc/logrotate.d/palaver) |
Troubleshooting
Doctor of one server.
sudo -u palaver-guild /opt/palaver/bin/palaverd -env /opt/palaver/instances/guild/palaverd.env doctor
What palaverd says.
sudo journalctl --namespace=palaver -u palaverd@guild -n 100
What nginx says.
sudo tail -n 50 /var/log/nginx/palaver/guild.error.log
| Symptom | Cause |
|---|---|
useradd: invalid user name |
name too long (at most 24 characters) |
| voice fails on this server only | key missing in livekit.yaml or LiveKit not restarted after step 7 |
| voice fails on every server | livekit.yaml broken by an edit: sudo journalctl --namespace=palaver -u livekit -n 50 |
| another server's site answers | server_name or the symlink in sites-enabled missing |
| web client without voice in the browser | CSP: wss://guild.palaver.social missing in palaver-web-guild.conf |