Question

I have an apache proxy for a meteor app and apache and meteor are on two separate machines. I need it that way as apache has to serve a lot of real websites and it wouldn't be a good idea to install the meteor app on this machine due to its limited resources.

However the WebSocket handshake fails with response code 400 "Can upgrade only to websocket" if I try to connect from the outside via the proxy. Everything works fine when I connect from within the LAN directly to the meteor machine. When WebSocket fails SockJS/Meteor falls back to XHR but unfortunately this brings up some bugs in the app in question. So I really need WebSocket to work in most of the cases.

I patched my apache installation with the patch mentioned here: https://stackoverflow.com/a/16998664 That looked like it went well but nothing changed...

My apache proxy directives currently are as follows:

ProxyRequests Off
ProxyPreserveHost On
ModPagespeed Off
<proxy>
Order deny,allow
Allow from all
</proxy>
ProxyPass / http://10.0.2.6:3000/
ProxyPassReverse / http://10.0.2.6:3000/

And I even know what's triggering the problem. The apache proxy messes around with the header. The original request header of the packet in question leaving my machine looks like this:

GET /sockjs/430/minw4r_o/websocket HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: myKey
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: My Agent

While the packet gets forwarded from the apache proxy like this:

GET /sockjs/430/minw4r_o/websocket HTTP/1.1
Host: example.com
Origin: http://example.com
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: myKey
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: My Agent
X-Forwarded-For: 24.xxx.xxx.xxx
X-Forwarded-Host: example.com
X-Forwarded-Server: example.com
Connection: Keep-Alive

So "Upgrade" gets removed and "Connection" altered and so the websocket handshake fails. Now I could try to always set "Upgrade" to "websocket" with a RequestHeader directive. However this doesn't feel right and I guess it would bring up other problems and so I was wondering if there is a real solution to this problem? Or is this something the patch from https://stackoverflow.com/a/16998664 should address and something went wrong on my end applying it?

From what I have read switching to nginx could make this setup easier. I will consider this but when possible I'd like to do this with apache as nginx would make other things more complicated and cost me a lot of time.

Was it helpful?

Solution

We use this for Apache and a SockJS app behind Apache. Apache is doing WebSocket proxy automatically, but you have to rewrite the scheme to ws otherwise it fallbacks to XHR. But only if the connection is a WebSocket handshake. Adding the following will fix your problem :) (note: change the localhost:3000 accordingly to your own backend url.

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^websocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade [NC]
RewriteRule .* ws://localhost:3000%{REQUEST_URI} [P]

OTHER TIPS

This answer is based on Fatih's answer. His solution fails for browsers that send a connection request header other than "Upgrade", such as "keep-alive, Upgrade". This was the case for me with Firefox 42.

To tackle the issue for Firefox also, change the apache RewriteCond as follows:

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://localhost:3000%{REQUEST_URI} [P]

(^Upgrade$ becomes Upgrade$)

I wanted to put this as a comment to Fatih's answer, however I lack the necessary reputation.

After reading several answers, posting on the Meteor forum, and a lot of trials here is the whole enchilada that worked for me. The other answers were somewhat incomplete, or at least didn't work for me.

I had to do:

sudo a2enmod proxy_wstunnel 

Also had to add a ProxyPass and ProxyPassReverse and changed ^Upgrade$ to Upgrade$ from another SO answer.

<VirtualHost *:80>
    ServerName  some-domain.com

    RewriteEngine on
    RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
    RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
    RewriteRule .* ws://localhost:3000%{REQUEST_URI} [P]

    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

</VirtualHost>

then restart Apache.

I checked on the console and there is no error now and no xhr requests. So I assume it's working correctly

I wish I were able to provide you a direct reply with apache instructions but since you have mentioned nginx and that the fact is, it is hard to configure, I'd like to weigh in with an alternative that actually uses nginx but shields you from all the complexities.

The tutorial at https://github.com/phusion/passenger/wiki/Phusion-Passenger:-Meteor-tutorial walks through the steps to set up Phusion Passenger with or without nginx (it internally uses nginx anyway) for multi-instance production Meteor deployments that can scale up to utilize all cores in your server.

It is as easy as:

$ cd meteor-app-directory
$ mkdir public tmp
$ passenger start

Fatih-Arslan's answer with Derwiwie's amendment worked like charm. One thing I had to use was putting wss instead of ws, because my service works only in https.

RewriteEngine on  
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]  
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]  
RewriteRule .* wss://localhost:3000%{REQUEST_URI} [P]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top