May 6, 2024Profile photoTony Masek

How to make Laravel Reverb work on Laravel Forge

I finally found the time to swap Pusher for Laravel Reverb. And while locally everything was straightforward, on production I got stuck for a really long time. I managed to make it work and I want to share the solution so that we can utilize this amazing tool such as Laravel Reverb.

Recently I faced an interesting issue. I wanted to swap Pusher for Laravel Reverb and everything went well locally. I followed the instructions and had everything up and running in no time. When trying to deploy it to production, however, I hit a snag.

I started going through documentation and there are basically three places where the issue can be: Laravel Reverb server configuration, Laravel Echo configuration and server configuration on Laravel Forge. The main problem is trying to figure out which part is the culprit and when consulting the docs I wasn't sure what to use as the source of truth, because the config is slightly different in different scenarios. It took me a while before I figured it out and in the end, the issue was incorrect configuration so I want to share what I've learned to save someone a headache.

If you just want to make it work scroll down to the I'm busy, give me TLDR section.

Making it work locally

So to start, let's make everything work locally first. To do that let's create a fresh Laravel project called websockets.

composer create-project laravel/laravel websockets

Now we can open docs for Reverb where it instructs us to run php artisan install:broadcasting and asks us a few questions:

  • Would you like to install Laravel Reverb?
    • Yes
  • Would you like to install and build the Node dependencies required for broadcasting?
    • No (At least in the context of this example, because we are creating just a websocket server without any UI)

Now we have config/reverb.php file and we also have prefilled variables inside our .env file.

REVERB_APP_ID=281879
REVERB_APP_KEY=afqmymcc0gazyeosr0dc
REVERB_APP_SECRET=bkbhjtc0iznnaej51dyy
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Now we can start the Reverb server by running the php artisan reverb:start artisan command and you should be able to connect. To test this I am using my other application I have locally which is using websockets. But for completeness here is the Echo config:

window.Pusher = Pusher;
window.Echo = new Echo({
    broadcaster: "reverb",
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT,
    wssPort: import.meta.env.VITE_REVERB_PORT,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? "https") === "https",
    enabledTransports: ["ws", "wss"],
});

Deploying the app to Laravel Forge

The steps below are not complete, because I want to slowly work towards to cause of the issue and the solution. I recommend you to read through it first before following the steps. Or skip to the I'm busy, give me TLDR section if you just want to make it work.

Now we have everything working locally. So let's deploy it. I guess most of you know this, but let's write it down anyway:

  • Provision the server
  • Get a (sub)domain
  • Set your DNS to point to your newly provisioned server
  • Push your repo to your source control provider
  • Create a new site with your (sub)domain
  • Secure the site with SSL certificate via Let's encrypt

The app is installed and now we need to make the Reverb work. If you go to the Application tab in your site detail, you will see the Laravel panel. Here we can turn on Laravel Reverb.

Now here comes the interesting part - it asks you to select public hostname, port and max concurrent connections. By default the public hostname will be your site name with ws. prefix. If you try to use the same hostname as your app it will say The host has already been taken. so let's go with the ws. prefix. We can leave the port at 8080 and the max connections at 1000. By doing this forge created a new daemon and added this line $FORGE_PHP artisan reverb:restart to your deployment script.

You will also have prepopulated .env variables. It looks the same as we had locally but with different REVERB_APP_ID, REVERB_APP_KEY and REVERB_APP_SECRET.

Making it work

First, we mustn't forget to create a DNS record for the new subdomain. Now if you try to connect from an unsecured environment it should work, but if we want to use a secure connection things will fall apart.

My sort of hacky solution

When I wasn't able to figure out the right combination of .env variables or Nginx configs I found these important notes in Forge docs:

Reverb Hostname Configuration
Forge ensures the hostname provided during Reverb's installation process is publicly accessible by adding a new server block to your existing site's Nginx configuration. This server block is contained within a new file and is not available to edit from the Forge UI dashboard.

SSL
If an SSL certificate exists for your site which protects Reverb's configured hostname, Forge will automatically install it when enabling Reverb, ensuring your Reverb server is accessible via secure WebSockets (wss)...

So this told me, that there is some Nginx config, which is not accessible via UI. And int the SSL part we can see, that if we are in a secured context we should use the following .env variables instead:

REVERB_PORT=443
REVERB_SCHEME=https

VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

So I was trying to find the generated Nginx and found it here /etc/nginx/forge-conf/<example.com>/after/reverb.conf. And when I inspected the contents I found out that it listens on port 80 instead of 443. So this was a clear indication, that I need to secure this Reverb subdomain first before using the toggle to activate it. To do that, we need to do the following few steps:

  1. Turn off the toggle for Laravel Reverb
  2. Remove SSL certificate
  3. Register an alias for our site with the ws. prefix
  4. Recreate an SSL certificate for our site + the ws. alias
  5. Re-enable Laravel Reverb
  6. Remove the ws. alias from the Nginx config

Let's explain each step in a bit more detail:

Turn off the toggle for Laravel Reverb

By turning off the toggle, Laravel Forge will remove the Reverb daemon and the reverb.conf file mentioned above.

Remove SSL certificate

We will have to issue a new certificate for our site as well as the alias

Register an alias for our site with the ws. prefix

Go to the Settings tab of your site and register a new alias which will be the hostname you will use with Reverb. So for example, if your app is at example.com you will register ws.example.com alias. Or if your app is at app.example.com the alias will be ws.app.example.com and so on...

Recreate SSL certificate for our site + the ws. alias

Come back to the SSL tab and issue a new certificate for both your site hostname as well as the alias. (It should be prefilled by default)

Re-enable Laravel Reverb

This will re-enable the daemon + recreate the reverb.conf but this time it will listen on port 443 as we want.

Side note: If you are having trouble with re-enabling Reverb with the same hostname as before and it says "The host has already been taken" just enable Reverb with some different subdomain, remove it and do it again.

Remove the ws. alias from Nginx config

And the last piece of the puzzle. Because we set the Reverb hostname as an alias, it now lives in our site Nginx config and it is being pointed to our app instead of the Reverb server. So just use the Edit files button and then Edit Nginx configuration and remove the alias on the line where it says server_name. So in the example where our app lives on example.com we should make the following change:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com ws.example.com;
    server_name example.com;
    server_tokens off;
    root /home/forge/example.com/public;
    # ...
}

TADA! We should be done! Now when the request comes in it will be forwarded to the Reverb server and everything should work as expected. Phew, that was quite a lot of text so I will make a TLDR version below to summarize:

I'm busy, give me TLDR

For the sake of this example, our site will have example.com domain and the Reverb server will have ws.example.com subdomain.

I'm provisioning a new server

  1. Provision your server
  2. Point DNS for example.com and ws.example.com to your server
  3. Create a new site for the domain example.com and register ws.example.com as an alias
  4. Install SSL for both the main domain and the alias (Forge should prefill this by default)
  5. Install your app from your source control provider
  6. Enable Reverb via toggle in the Laravel panel on the Application tab. Tweak the settings to your needs, but the default should be all right if you are using the ws. prefix for your site
  7. Check .env variables to make sure REVERB_PORT=443 and REVERB_SCHEME=https. It should be already set because the subdomain for Reverb (the alias) already has an SSL certificate
  8. Use the Edit files -> Edit Nginx Configuration and remove the alias here as follows:
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com ws.example.com;
    server_name example.com;
    server_tokens off;
    root /home/forge/example.com/public;
    ...
}
  1. You should be good to go!

I already have the server + site installed

  1. Make sure you have disabled the Laravel Reverb toggle
  2. Remove your SSL certificate
  3. Go to the Settings tab for your site and add a new alias you will use as the hostname for your Reverb server. Usually the site name with ws. prefix so ws.example.com
  4. Make sure you have a valid DNS record for your alias pointing to your server
  5. Issue an SSL certificate for your site as well as the alias (this should be prefilled by Forge)
  6. Turn on the Reverb integration via the toggle
  7. Check .env variables to make sure REVERB_PORT=443 and REVERB_SCHEME=https
  8. Use the Edit files -> Edit Nginx Configuration and remove the alias here as follows:
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com ws.example.com;
    server_name example.com;
    server_tokens off;
    root /home/forge/example.com/public;
    ...
}
  1. You should be good to go!

Summary

Thanks for reading, I hope I could save you some time because trust me it took me much longer to figure it out than I care to admit. On top of that I was using a TLD which has HSTS so it does not work outside of a secure context hence I wasn't even able to tell whether it was working with the default setup.

I'm not saying this is the correct solution so if you find any issues or improvements, please definitely reach out to me (or DM me) on Twitter and I will gladly update the post.

Until next time,
Tony