Skip to main content
This page takes a server (DEV, TEST or PROD on Plesk) or a developer machine from “chatrooms not installed” to “messages arrive live”. It is written for the current version of the application:
No real value appears on this page. Every key, secret, password and token below is a placeholder (<generated>, <token>, …). Generate your own values on each server, keep them in that server’s .env only, and never commit a .env file. If a real .env was ever pasted into a chat, a ticket or a screenshot, rotate the keys it holds (R2, mail, payment, JWT, database).

1. What you need, and what you don’t


2. How the pieces talk

Solid lines exist in both modes. Dotted lines exist only in live mode (BROADCAST_DRIVER=reverb and a running Reverb process). Port 8080 never leaves the machine: nginx and Laravel reach it on 127.0.0.1. What happens when the instructor sends a message:

3. Install on a server (DEV, TEST, PROD)

Do these steps once per server, over SSH, as the site’s system user (Plesk → the domain → Hosting settings shows it). At the end the chatrooms work in polling mode.
1

Open the backend folder and pick the right PHP

In Plesk, check that the domain runs PHP 8.2 too (Websites & Domains → the domain → PHP Settings). A domain on an older PHP fails on the new chatroom classes while the command line works.
2

Get the code

3

Install the packages

The pcntl and sockets PHP extensions are recommended for the Reverb process ($PHP -m | grep -E "pcntl|sockets"), not required.
4

Add the chatroom block to .env

Open the server’s .env and add this block at the end. Do not add a second BROADCAST_DRIVER line: if the file already has one, delete it and keep the one in this block (section 4.1 explains why).
Generate the three Reverb values, one set per environment (never reuse PROD’s on TEST). They are just random strings, so they don’t have to be generated on the server: your own computer works as well, which is the easy way when SSH on the server is limited.
In a macOS or Linux terminal, this prints the three lines ready to paste, with only lowercase letters and digits (what Reverb and the .env file need):
If PHP gives you trouble, this version uses only openssl:
Run it once per environment, so TEST and PROD get different values. Don’t save the output anywhere else, and don’t paste it into a chat or a ticket.
Paste them without SSH. Plesk → Files → open the backend folder → .env → Edit in Text Editor. Replace the three empty REVERB_APP_* lines with the printed ones and save. Then clear the config cache (next steps; without SSH, see “Run an artisan command without SSH” in the cache step).
Two common causes, and the error tells you which one:
  • -r: command not found: $PHP is empty. The line PHP=/opt/plesk/php/8.2/bin/php (step 1) has to run first in the same SSH session; a new session forgets it. Plain php -r '…' also works when the system php is recent enough.
  • No such file or directory for /opt/plesk/php/8.2/bin/php: the SSH shell is chrooted, a locked-down shell that cannot see Plesk’s PHP. A Plesk admin can change it under the domain’s Hosting settings: set “Access to the server over SSH” to /bin/bash instead of /bin/bash (chrooted). Generating the keys on your computer avoids the problem altogether.
Leave the rest of .env as it is. Do not run php artisan reverb:install: it appends its own variables to .env and rewrites config/broadcasting.php, and everything it does is already in the code.
5

Create the tables

Run migrate on the server: run from a laptop it migrates the laptop’s database. Rollback, if ever needed: $PHP artisan migrate:rollback --step=2 (drops the four chatroom tables with their messages).
6

Clear the caches and restart PHP

Then restart the domain’s PHP so OPcache drops the old files: Plesk → the domain → PHP Settings → Apply (re-saving restarts the handler), or as root systemctl restart plesk-php82-fpm (PHP-FPM; a domain on FastCGI under Apache needs an Apache restart instead). Do this after every pull and every .env change: a cached config ignores .env until optimize runs again.Run an artisan command without SSH. Plesk → Scheduled Tasks → Add Task → Run a command, with the line below, then Run Now. Delete the task afterwards. The same trick works for optimize and the other artisan commands of this page. If your Plesk has the Laravel Toolkit, its Artisan tab does the same thing.
7

Allow 20 MB images

  • Plesk → the domain → PHP Settings: upload_max_filesize = 25M and post_max_size = 25M.
  • Plesk → the domain → Apache & nginx Settings → Additional nginx directives:
Without them a large image answers 413 (nginx) or 422 “failed to upload” (PHP).
8

Run the scheduler

Plesk → Scheduled Tasks → Add task → Run a command, every minute (* * * * *), as the site’s user:
It runs chatrooms:prune-attachments every night (images uploaded but never sent, older than 24 hours) and the existing notification clean-up.
9

Check

From your machine, with a token of any signed-in user (from POST /api/auth/login; use a test account, never paste a real password into a shared script):
Then in the app: create a classroom, Save & next, Create chatroom on step 2, send a message. It shows up on the other side within about 5 seconds.
Testing by hand with curl? Always send a body on a POST (-d '{}'). The Plesk web firewall refuses a body-less POST with an HTML 403 Forbidden page, and a few of those get your IP banned for a while (every request then times out). The apps always send a body, so they are not affected. Unban in Plesk → Tools & Settings → IP Address Banning.

4. The .env, explained

4.1 One line per key

Laravel reads .env from top to bottom and the last line wins. A file with BROADCAST_DRIVER=reverb near the top and BROADCAST_DRIVER=log in the chatroom block runs with log. Keep exactly one line per key; the same applies to any other key that appears twice.

4.2 What each variable means

Optional, the defaults are fine: REVERB_APP_PING_INTERVAL (60 s), REVERB_APP_ACTIVITY_TIMEOUT (30 s), REVERB_APP_MAX_MESSAGE_SIZE (10 000 bytes; chat events stay under it, a long message is sent as its id and the apps fetch it), REVERB_MAX_REQUEST_SIZE (100 000 bytes).

4.3 Keys the chatrooms rely on but that already exist


5. Make messages instant: run Reverb

Without this section the chat works by polling. With it a message reaches the other side in milliseconds.
1

Try Reverb by hand

In a second SSH shell, send a test event (the prefix forces Reverb while .env still says log; it works when the config is not cached, otherwise do this check after the switch step):
The first shell logs a block for private-chatroom.0. Stop it with Ctrl+C. Never use --debug under supervisor (it prints every frame).
2

Keep it running

Plesk’s Scheduled Tasks cannot keep a process alive, so this needs root once. Pick one:
One program per environment. When two environments share a server, give each its own port (for example TEST 8080, DEV 8081) and use it in that environment’s REVERB_SERVER_PORT, REVERB_PORT and nginx block.
3

Proxy the WebSocket through nginx

Plesk → Websites & Domains → the backend domain → Apache & nginx Settings → Additional nginx directives.That box is one field for the whole vhost. It holds the upload limit of the “Allow 20 MB images” step, the /storage/ CORS block that lets the Flutter web app draw local-storage images (see File Management & Storage), and the chatroom block. The two location blocks do not conflict: each one handles its own path. Add only what is missing; the full box reads:
  • ^~ makes nginx stop at that prefix, so none of Plesk’s pattern-based locations (such as “Serve static files directly by nginx”) takes the request first. A plain location /app/ works in practice too; ^~ is the safer form.
  • X-Forwarded-For passes the browser’s address on to Reverb, and proxy_send_timeout gives sends the same 120 s as reads.
  • 8080 is REVERB_SERVER_PORT; use the environment’s own port when several share a server.
  • Plesk checks the syntax and reloads nginx when you press OK.
Check the proxy from your machine:
A plain curl -i without the upgrade headers answers 500 "Internal server error." from Reverb once the proxy works: that is expected, Reverb only speaks WebSocket there.Keep port 8080 closed in the firewall; only nginx and Laravel on the same machine use it.
4

Switch the app to live

In .env:
Open the same chatroom in two browsers (a learner and the instructor): a message appears on the other side at once, and the learner’s composer locks the moment the instructor turns Learners can write off. The apps need no rebuild.
After every later deploy (Reverb runs the code it started with):
Switching back to polling: BROADCAST_DRIVER=log, then optimize:clear and optimize. The apps poll again within 5 seconds; Reverb can keep running.

6. Redis: not needed, and when it would be

The chatrooms never use Redis today:
  • Reverb keeps every connection of one process in memory. Redis only comes in with REVERB_SCALING_ENABLED=true, to pass events between several Reverb processes on several machines behind a load balancer.
  • Laravel sends the chat events during the request, so no queue (and no Redis queue) is involved.
  • Cache and sessions stay on file.
  1. Run a Redis server reachable by every Reverb machine (apt install redis-server, protected by a password and a firewall).
  2. In every backend .env:
  3. optimize:clear, optimize, then reverb:restart on every machine.
Reverb talks to Redis with its own bundled client (clue/redis-react), so no PHP Redis extension is needed for this. Using Redis for the cache or the queue is a separate decision, unrelated to the chatrooms.

7. Values per environment


8. Developer machine

  • The Flutter web app on localhost:5000 with Environment.localhost in lib/core/constants/constants.dart connects to ws://127.0.0.1:8080 directly (Reverb accepts every origin).
  • Android emulator: REVERB_CLIENT_HOST=10.0.2.2, the same host the emulator uses for the API.
  • After changing the API base URL run dart run build_runner build -d: the URL is baked into the generated Retrofit client.
  • Tests never need Reverb: DB_CONNECTION=sqlite DB_DATABASE=:memory: php artisan test --filter=Chatroom.

9. The Flutter app

Nothing to configure for Reverb: the app asks GET /api/realtime/config when a conversation opens, connects when enabled is true, polls every 5 seconds otherwise, and catches up after every reconnection. What it needs is the build that contains the chatrooms:
Constants.currentEnvironment decides which backend the build talks to; set it before building for a server. A running flutter run needs a full restart (not a hot reload) after pulling provider changes.

10. Troubleshooting

Where to look: storage/logs/laravel.log (API side, failed broadcasts included) and the supervisor stdout_logfile (Reverb side). The backend repository’s CHATROOMS.md holds the same steps for offline use.

11. Security checklist

  • .env stays on the server, outside git. This page and the repositories contain placeholders only.
  • One Reverb key set per environment. REVERB_APP_KEY is public by design; REVERB_APP_SECRET never leaves the server.
  • Reverb listens on 127.0.0.1 only; the outside reaches it through nginx and HTTPS.
  • Every channel subscription is signed by the backend (POST /api/broadcasting/auth, behind the login) and only members and the company’s admins are admitted.
  • APP_DEBUG=false on production.
  • A key that was shared outside the server (chat, ticket, screenshot) is rotated: generate a new value, update .env, optimize, reverb:restart.