thdebay's avatar

How to configure Traefik and Nginx proxy for Laravel Reverb?

I would appreciate a little help to help me setup the traefik/nginx config so that the Laravel Reverb requests are properly routed to the Reverb server.

My app is dockerized and served through Traefik and an Nginx server.

This is what I tried in my docker-compose file. My idea was to setup a subdomain on ws.v3.localhost for the Reverb requests and to have Traefik add the /ws prefix for Nginx to know how to handle these requests.

The "normal" requests are made on ttfr.v3.localhost and should go to the Laravel app.

  laravel:
(...)
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.laravel.entrypoints=web"
      - "traefik.http.routers.laravel.rule=Host(`ttfr.v3.localhost`)"
      - "traefik.http.middlewares.ws-prefix.addprefix.prefix=/ws"
      - "traefik.http.routers.laravel-ws.rule=Host(`ws.v3.localhost`)"
      - "traefik.http.routers.laravel-ws.middlewares=ws-prefix"
      - "traefik.http.services.laravel-ws.loadbalancer.server.port=80"

Then I have this nginx config file,

server {
    listen 80;

    index index.php index.html;

    charset utf-8;

    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    root /var/www/app/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header 'Content-Security-Policy' 'upgrade-insecure-requests';
    add_header 'Access-Control-Expose-Headers' 'X-Inertia';

    location /ws/ {
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        access_log /var/log/nginx/ws_access.log;
        error_log /var/log/nginx/ws_error.log;

        proxy_pass http://localhost:8088;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
        proxy_set_header X-Forwarded-Proto https;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass localhost:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

The reverb server is running on 0.0.0.0:8088

For debugging purposes I tried to bind the port 8088 directly to the container in my docker-compose config, in this case the Reverb requests are sent directly to localhost:8088 and it works. But when I use the config above (so that my app uses ws.v3.localhost on port 80 instead of port 8088), it does not work anymore.

Here is the Reverb config I'm using in my .env

REVERB_HOST=ws.v3.localhost
REVERB_PORT=80
REVERB_SCHEME=http

REVERB_SERVER_HOST=0.0.0.0
REVERB_SERVER_PORT=8088

Does my approach make sense? Is there something I'm missing? I'd appreciate any tips on how to debug this and identify where the problem comes from.

0 likes
3 replies
LaryAI's avatar
Level 58

It looks like you're on the right track with your configuration, but there might be a few adjustments needed to ensure that the traffic is properly routed through Traefik to your Nginx server and then to the Reverb server. Here are some steps and modifications you can consider:

  1. Traefik Configuration: Ensure that Traefik is correctly configured to route requests to the appropriate services. You might need to adjust the Traefik labels in your docker-compose.yml file to ensure that the WebSocket requests are correctly handled.

  2. Nginx Configuration: Your Nginx configuration seems mostly correct, but ensure that the proxy_pass directive in the /ws/ location block is pointing to the correct internal service URL provided by Docker.

Here's a revised approach:

Docker Compose Configuration

Make sure your Traefik service is set up to handle both HTTP and WebSocket requests. Here's an example snippet from your docker-compose.yml:

services:
  traefik:
    image: traefik:v2.3
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"  # Traefik dashboard
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

  laravel:
    image: your-laravel-image
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.laravel.entrypoints=web"
      - "traefik.http.routers.laravel.rule=Host(`ttfr.v3.localhost`)"
      - "traefik.http.routers.laravel-ws.entrypoints=web"
      - "traefik.http.routers.laravel-ws.rule=Host(`ws.v3.localhost`) && PathPrefix(`/ws`)"
      - "traefik.http.services.laravel-ws.loadbalancer.server.port=80"
    depends_on:
      - traefik

Nginx Configuration

Ensure that your Nginx configuration is set to handle the WebSocket upgrade requests correctly. Here's the relevant part of your Nginx configuration:

location /ws/ {
    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_pass http://reverb:8088;  # Ensure this points to your Reverb service
}

Debugging Tips

  1. Check Traefik Dashboard: Access the Traefik dashboard on http://localhost:8080 to see how routes are being configured and ensure that your services are visible and correctly routed.

  2. Logs: Check both Traefik and Nginx logs for any errors or warnings that might indicate misconfiguration or network issues.

  3. Direct Requests: Temporarily modify your /etc/hosts file to point ws.v3.localhost to your local machine and try accessing http://ws.v3.localhost/ws directly in a browser or using a tool like curl to see if the routing works outside of your application context.

By following these steps and ensuring that each component is correctly configured, you should be able to route WebSocket requests through Traefik to Nginx and then to your Reverb server effectively.

nuxnux's avatar

Hey have you sucessfully setup reverb on production with traefik ?

HakimFIDJEL's avatar

@nuxnux Hey ! I just setup reverb with traefik in production I hope it will help, also what i did is creating a subdomain to host the reverb server, here is my code :

  • docker-compose.yaml : """ services: my-app: image: my-image:latest volumes: - .env:/var/www/html/.env - storage_volume:/var/www/html/storage entrypoint: /usr/local/bin/entrypoint-app.sh labels: - "traefik.enable=true" - "traefik.docker.network=traefik-network" - "traefik.http.routers.app.rule=Host(my-domain.com)" - "traefik.http.routers.app.entrypoints=websecure" - "traefik.http.routers.app.tls.certresolver=myresolver" - "com.centurylinklabs.watchtower.enable=true" depends_on: - mysql restart: always networks: - my-network - traefik-network

Traefik

traefik-proxy: image: traefik:v3.1 command: - "--providers.docker" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=traefik-network" - "--certificatesresolvers.myresolver.acme.tlschallenge=true" - "--certificatesresolvers.myresolver.acme.email=[email protected]" - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" - "--entrypoints.web.address=:80" - "--entryPoints.websecure.address=:443" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" ports: # The HTTP Port - "80:80" # The HTTPS Port - "443:443" volumes: - letsencrypt:/letsencrypt #So that Traefik can listen to Docker events - /var/run/docker.sock:/var/run/docker.sock networks: - traefik-network restart: on-failure

MySQL database container

mysql: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysql - .env:/run/secrets/.env
environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_DATABASE: ${DB_DATABASE} healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 restart: always networks: - my-network

Laravel Reverb container

my-reverb: image: my-image:latest volumes: - .env:/var/www/html/.env entrypoint: /usr/local/bin/entrypoint-reverb.sh labels: - "traefik.enable=true" - "traefik.docker.network=traefik-network" - "traefik.http.routers.reverb.rule=Host(websocket.my-domain.com)" - "traefik.http.routers.reverb.entrypoints=websecure" - "traefik.http.routers.reverb.tls.certresolver=myresolver" - "traefik.http.routers.reverb.tls=true" - "traefik.http.services.reverb.loadbalancer.server.port=8080" - "traefik.http.services.reverb.loadbalancer.server.scheme=http" depends_on: mysql: condition: service_healthy restart: always networks: - my-network - traefik-network

networks: jcoaching-network: driver: bridge traefik-network: external: true

volumes: mysql_data: storage_volume: letsencrypt: """,

.env : """ REVERB_SERVER_HOST=0.0.0.0 REVERB_APP_ID=<app_id> REVERB_APP_KEY=<app_key> REVERB_APP_SECRET=<app_secret> REVERB_HOST="websocket.my-domain.com" REVERB_PORT=443 REVERB_SCHEME=https

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" VITE_REVERB_HOST="${REVERB_HOST}" VITE_REVERB_PORT="${REVERB_PORT}" VITE_REVERB_SCHEME="${REVERB_SCHEME}" """ echo.js :

""" import Echo from 'laravel-echo'; import Pusher from 'pusher-js';

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 ?? 80, wssPort: import.meta.env.VITE_REVERB_PORT ?? 443, forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', enabledTransports: ['ws', 'wss'], }); """,

entrypoint-app.sh : """ #!/bin/bash

php artisan config:clear php artisan cache:clear php artisan route:clear php artisan view:clear

php artisan config:cache php artisan route:cache php artisan view:cache

npm run build

echo "Starting Laravel queue worker..." php artisan queue:work --daemon --sleep=3 --tries=3 --timeout=60 &

echo "Starting Apache server..." apache2-foreground

""",

entrypoint-reverb.sh :

""" #!/bin/bash

php artisan config:clear php artisan cache:clear php artisan route:clear php artisan view:clear

php artisan config:cache php artisan route:cache php artisan view:cache

echo "Starting Reverb server..." php artisan reverb:start --debug

"""

I tried to learn from scratch (same for docker ^^) so they may be some common use mistakes but at least it works :))

Please or to participate in this conversation.