Introduction

ActionCable performs origin checking to prevent cross-site WebSocket hijacking. In production, the WebSocket connection is rejected with a 403 or the connection fails entirely if the request origin does not match the configured allowed_request_origins. This commonly happens when deploying behind a reverse proxy, using a CDN, or when the frontend is served from a different domain.

Symptoms

  • WebSocket connection shows failed: Error during WebSocket handshake in browser console
  • Rails logs show Request origin not allowed: https://example.com
  • ActionCable consumer disconnects immediately after connecting
  • Real-time features (notifications, chat, live updates) do not work
  • Browser console shows 403 Forbidden on /cable endpoint

Rails log output: `` Started GET "/cable" [WebSocket] for 10.0.0.1 at 2026-04-09 10:15:00 +0000 Request origin not allowed: "https://app.example.com" Failed to upgrade to WebSocket (Request origin not allowed)

Common Causes

  • allowed_request_origins not configured for production domain
  • Frontend served from different domain than Rails API
  • HTTPS/HTTP mismatch in origin configuration
  • Nginx proxy stripping or modifying Origin header
  • Using IP address instead of domain name in development

Step-by-Step Fix

  1. 1.Configure allowed_request_origins for production:
  2. 2.```ruby
  3. 3.# config/environments/production.rb
  4. 4.config.action_cable.allowed_request_origins = [
  5. 5."https://example.com",
  6. 6."https://www.example.com",
  7. 7."https://app.example.com",
  8. 8."https://admin.example.com",
  9. 9.# Add staging if shared environment
  10. 10."https://staging.example.com",
  11. 11.]
  12. 12.`
  13. 13.Use environment variable for dynamic configuration:
  14. 14.```ruby
  15. 15.# config/environments/production.rb
  16. 16.config.action_cable.allowed_request_origins =
  17. 17.ENV.fetch("CABLE_ALLOWED_ORIGINS", "https://example.com").split(",")
  18. 18.`

Set in deployment environment: ``bash # In .env, systemd, or PaaS config CABLE_ALLOWED_ORIGINS=https://example.com,https://app.example.com,https://admin.example.com

  1. 1.Fix Nginx proxy configuration:
  2. 2.```nginx
  3. 3.location /cable {
  4. 4.proxy_pass http://rails_backend;
  5. 5.proxy_http_version 1.1;
  6. 6.proxy_set_header Upgrade $http_upgrade;
  7. 7.proxy_set_header Connection "Upgrade";
  8. 8.proxy_set_header Host $host;
  9. 9.proxy_set_header X-Real-IP $remote_addr;
  10. 10.proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  11. 11.proxy_set_header X-Forwarded-Proto $scheme;
  12. 12.# Preserve Origin header
  13. 13.proxy_set_header Origin $http_origin;
  14. 14.proxy_read_timeout 86400s;
  15. 15.proxy_send_timeout 86400s;
  16. 16.}
  17. 17.`
  18. 18.Handle different frontend and API domains:
  19. 19.```javascript
  20. 20.// frontend app JavaScript - include credentials
  21. 21.const consumer = ActionCable.createConsumer(
  22. 22."wss://api.example.com/cable"
  23. 23.);

// Rails config - add frontend domain to allowed origins config.action_cable.allowed_request_origins = [ "https://frontend.example.com", # Where the SPA is served "https://api.example.com", # Where ActionCable runs ] ```

  1. 1.Disable origin check for internal services only:
  2. 2.```ruby
  3. 3.# config/cable.yml
  4. 4.production:
  5. 5.adapter: redis
  6. 6.url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  7. 7.channel_prefix: myapp_production
  8. 8.# Only disable for trusted internal traffic
  9. 9.disable_request_forgery_protection: <%= Rails.env.test? %>
  10. 10.`

Prevention

  • Automate origin configuration from deployment environment variables
  • Add WebSocket connectivity checks to health check endpoints
  • Monitor ActionCable connection success rate in application metrics
  • Test WebSocket connections in staging before production deployment
  • Document all frontend domains that connect to ActionCable
  • Use Rails.application.config.action_cable.allowed_request_origins consistently across environments