Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is a custom Rackup file still required when running the Action Cable server on a subdomain? #111

Open
JamesChevalier opened this issue Sep 10, 2021 · 0 comments

Comments

@JamesChevalier
Copy link

I am trying to configure ActionCable in Rails 6.1.4.1 to run on a subdomain, but when I follow these instructions the env["warden"] (which is used by Devise) is nil in app/channels/application_cable/connection.rb.

This post will explain my environment/configuration for my attempts at getting things to work with a custom Rackup file, and then it will explain the changes I made to get things working. I've separated the sections with horizontal lines, for easier skimming.

This is a rather long post, so I want to thank you ahead of time for even reading this far.


  • ActionCable is fully functional outside of any channels that expect authentication (so anything that is not using env["warden"])
  • ActionCable is fully functional (including use of env["warden"]) if I do not use a subdomain (if I use a location /cable block within my main server block)
  • The site itself (main domain & subdomain) is fully functional - I can log into the site & visit either main or subdomain and be logged into both

I have the main domain (and www) proxied through CloudFlare, but I do not have the actioncable subdomain proxied.
My web infrastructure consists of a single EC2 instance.

I have ActionCable mounted in my routes file:

Rails.application.routes.draw do
  mount ActionCable.server => "/cable"
end

I have the session_store set to share across subdomains, and the URL & allowed_request_origins set in my production.rb file:

config.session_store :cookie_store, key: "_app_session", same_site: :lax, domain: ".appdomain.com", tld_length: 2
config.action_cable.url = "wss://actioncable.appdomain.com/cable"
config.action_cable.allowed_request_origins = ["https://appdomain.com", "https://actioncable.appdomain.com", /https:\/\/*.appdomain.com/]

I have a cable/config.ru file specifically for the ActionCable server:

require_relative "../config/environment"
Rails.application.eager_load!
run ActionCable.server

My Nginx config forces SSL & non-www, and runs ActionCable separately:

# Force SSL
server {
  listen      80;
  listen      [::]:80;
  return      301 https://appdomain.com$request_uri;
}

# Force non-www in SSL
server {
  listen [::]:443 ssl http2;
  listen      443 ssl http2;
  server_name www.appdomain.com;
  gzip off;
  ssl_certificate     /etc/letsencrypt/live/appdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/appdomain.com/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
  ssl_ecdh_curve secp384r1;
  ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; # Score=90 (recommended)
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_prefer_server_ciphers on;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 10s;
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  return 301 https://appdomain.com$request_uri;
}

# The actual site
server {
  listen [::]:443 ssl http2;
  listen      443 ssl http2;
  server_name appdomain.com;
  gzip off;
  ssl_certificate     /etc/letsencrypt/live/appdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/appdomain.com/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 
  ssl_ecdh_curve secp384r1; 
  ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; # Score=90 (recommended)
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_prefer_server_ciphers on;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 10s;
  add_header X-Frame-Options SAMEORIGIN;
  add_header X-Content-Type-Options nosniff;
  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }
  error_page 500 502 503 504 /500.html;
  keepalive_timeout 10;
  passenger_enabled on;
  passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
  passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
  passenger_app_env production;
  passenger_min_instances 4;
  root /home/myself/production/current/public;
  # ActionCable; allowing this because I'm moving from subfolder to subdomain & have active clients
  location /cable {
#    return 302 https://actioncable.appdomain.com/cable; # Once this is working, I plan on forcing clients over to the subdomain
    passenger_app_group_name app_action_cable_f;
    passenger_force_max_concurrent_requests_per_process 0;
  }
}

# ActionCable
server {
  listen 443;
  server_name actioncable.appdomain.com;
  root /home/myself/production/current/public;
  passenger_enabled on;
  passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
  passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
  passenger_app_env production;
  passenger_app_type rack;
  passenger_min_instances 4;
  passenger_startup_file cable/config.ru;
  location /cable {
    passenger_app_group_name app_action_cable_sd;
    passenger_force_max_concurrent_requests_per_process 0;
  }
}

Things do work properly if I comment out these custom Rackup file lines in the ActionCable server in nginx:

passenger_app_type rack;
passenger_startup_file cable/config.ru;

This effectively means that I'm running two instances of my Rails app, so if I want to keep traffic out of the subdomain, I can also add a redirect before the location /cable line, resulting in this server block:

server {
  listen 443;
  server_name actioncable.appdomain.com;
  root /home/myself/production/current/public;
  passenger_enabled on;
  passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
  passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
  passenger_app_env production;
  passenger_min_instances 4;
  location / {
    return 301 https://appdomain.com$request_uri;
  }
  location /cable {
    passenger_app_group_name app_action_cable_sd;
    passenger_force_max_concurrent_requests_per_process 0;
  }
}

An aside: I don't understand why none of the ssl-related lines are required in my ActionCable server instance. 🤷 Everything works without specifying ssl_certificate etc within that ActionCable server block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant