Turbostream websockets can't connect when a CDN with WAF is being used

Hey all

I’m using Rails 7 + Hotwire, hosting on Heroku using Expedited CDN.

Expedited is great and easy to configure so far EXCEPT WAF doesn’t support Websocket connections. Thus, I’ve recently been getting WebSocket connection to 'wss://<website-url>/cable' failed.

One of the suggestions from Expedited was to create a subdomain that accepts and handles the Websocket connections by bypassing the CDN, but I have no idea how to do that and the documentation for Websockets for Turbo is… non-existent practically.

Does anyone know how to configure this?

  • a subdomain that accepts websocket connections
  • which DNS records I’d need to create to make it work (I’m using GoDaddy, my DNS knowledge is low)
  • any Heroku configuration/records I might need to add

Cheers

I’ve had a try at configuring ActionCable to point directly to my Rails app, but I still can’t quite get it working.

I’ve tried…

config.action_cable.url = "wss://socket.<my-domain>.io/cable"

# application.html.erb
<%= action_cable_meta_tag %>

then pointing a CNAME record with socket as the value to my Heroku DNS (not the CDN one), which doesn’t work.


I then went “why use the DNS as all when I can just make the action_cable.url the same as my Heroku app/DNS directly?”, which didn’t work

config.action_cable.url = "wss://<my-production-app-dns>.herokudns.com/cable"

and…

config.action_cable.url = "wss://<my-production-app-url>.herokuapp.com/cable"

which all don’t work. From my understanding this should bypass the CDN completely and hit my server’s /cable endpoint directly, but it still throws the same error (albeit, with the action_cable.url specified instead of my regular domain).

I think I’ve solved it.

# Procfile
cable: bundle exec puma -p 28080 cable/config.ru

# cable/config.ru
require_relative "../config/environment"
Rails.application.eager_load!

run ActionCable.server

# config/environments/production.rb
# ...
  # Mount Action Cable outside main process or domain.
  config.action_cable.mount_path = nil
  config.action_cable.url = ENV["HEROKU_ENV"] == "staging" ? (
    "wss://staging.<domain>.io:28080"
  ) : (
    "wss://<domain>.io:28080"
  )

I guess the config.action_cable.url fires directly to the wss:// url and doesn’t ever hit the DNS proxy.