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 handshakein 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
/cableendpoint
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_originsnot 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.Configure allowed_request_origins for production:
- 2.```ruby
- 3.# config/environments/production.rb
- 4.config.action_cable.allowed_request_origins = [
- 5."https://example.com",
- 6."https://www.example.com",
- 7."https://app.example.com",
- 8."https://admin.example.com",
- 9.# Add staging if shared environment
- 10."https://staging.example.com",
- 11.]
- 12.
` - 13.Use environment variable for dynamic configuration:
- 14.```ruby
- 15.# config/environments/production.rb
- 16.config.action_cable.allowed_request_origins =
- 17.ENV.fetch("CABLE_ALLOWED_ORIGINS", "https://example.com").split(",")
- 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.Fix Nginx proxy configuration:
- 2.```nginx
- 3.location /cable {
- 4.proxy_pass http://rails_backend;
- 5.proxy_http_version 1.1;
- 6.proxy_set_header Upgrade $http_upgrade;
- 7.proxy_set_header Connection "Upgrade";
- 8.proxy_set_header Host $host;
- 9.proxy_set_header X-Real-IP $remote_addr;
- 10.proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- 11.proxy_set_header X-Forwarded-Proto $scheme;
- 12.# Preserve Origin header
- 13.proxy_set_header Origin $http_origin;
- 14.proxy_read_timeout 86400s;
- 15.proxy_send_timeout 86400s;
- 16.}
- 17.
` - 18.Handle different frontend and API domains:
- 19.```javascript
- 20.// frontend app JavaScript - include credentials
- 21.const consumer = ActionCable.createConsumer(
- 22."wss://api.example.com/cable"
- 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.Disable origin check for internal services only:
- 2.```ruby
- 3.# config/cable.yml
- 4.production:
- 5.adapter: redis
- 6.url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
- 7.channel_prefix: myapp_production
- 8.# Only disable for trusted internal traffic
- 9.disable_request_forgery_protection: <%= Rails.env.test? %>
- 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_originsconsistently across environments