ws: Websockets not working with HTTPS in an Apache Proxy (302 error?)

Hello, first of all, thanks for your awesome library. I was able to setup and get WS running in a breeze. Thanks.

I have a problem with ws, I’m unable to make websockets work on an apache proxy through HTTPS. Websockets are working properly if no (apache) http(s) proxy is used.

My setup: I have an apache server with multiple virtual hosts. I have a HTTPS webpage for myserver.com and the HTTPS API with node/express/ws in api.myserver.com subdomain through the proxy, that redirects the requests to the node.js instance (multiple instances on PM2) running on port 3333.

This is my apache virtual host for the subdomain:

<VirtualHost *:443>
	ServerName api.myserver.com
	ServerAdmin hello@myserver.com
	DocumentRoot /var/www/html/myserver/api
        Options -Indexes

	SSLEngine on                                                                
	SSLProtocol all -SSLv2                                                      
	SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM                

	SSLCertificateFile /etc/apache2/certs/STAR_myserver_co.crt
	SSLCertificateKeyFile /etc/apache2/certs/myserver_private_key.pem
	SSLCertificateChainFile /etc/apache2/certs/STAR_myserver_co.ca-bundle
	
	SSLProxyEngine On
	ProxyPreserveHost On
	ProxyRequests Off

	# This is for websocket requests
	ProxyPass /wss/ wss://localhost:3333/
	ProxyPassReverse /wss/ wss://localhost:3333/
	
        # This is for normal requests    
        ProxyPass / https://localhost:3333/
        ProxyPassReverse / https://localhost:3333/
</VirtualHost>

This works OK for redirecting the connections to the node express backend. I have installed mod_proxy, mod_proxy_http and mod_proxy_wstunnel.

This is the node.js API backend: first, I initialize express, sessions, etc.

// express, session and mongodb session storage
var express = require('express')
var session = require('express-session')
var MongoStore = require('connect-mongo')(session)
var app = express()
// configure sessionStore and sessions, mongodb, etc...

// Certificates and credentials for HTTPS server
var fs = require('fs')
var privateKey  = fs.readFileSync(__dirname + '/certs/myserver_private_key.pem', 'utf8')
var certificate = fs.readFileSync(__dirname + '/certs/myserver_cert.pem', 'utf8')
var ca = fs.readFileSync(__dirname + '/certs/myserver_ca.pem', 'utf8')
var credentials = {key: privateKey, cert: certificate, ca: ca}

app.enable('trust proxy')
app.set("trust proxy", 1)

And then I setup the HTTPS server securely, using the same certificates that in APACHE:

// Setup HTTPS server
var https = require('https')
var server = https.createServer(credentials, app)
server.listen(appPort, 'localhost', function () {
    // Server up and running!
    var host = server.address().address
    var port = server.address().port
    console.log('myserver listening at https://%s:%s', host, port)
})

Last, I setup the websocket connections:

// setup Websockets
wss = new WebSocketServer({ server: server })
wss.on('connection', function connection(ws) {
	var cookies = cookie.parse(ws.upgradeReq.headers.cookie)
	var sid = cookieParser.signedCookie(cookies["connect.sid"], myserver_secret)
	// extract user credentials and data from cookie/sid,

	// get the session object
	sessionStore.get(sid, function (err, ss) {
		...
	})
})

Then my clients just try to connect to websockets securely (because, being a HTTPS app, I cannot use the ws:// insecure websockets connection):

window.WebSocket = window.WebSocket || window.MozWebSocket
webSocket = new WebSocket('wss://' + location.host + '/wss')

And then I get always the same error 302:

[Error] WebSocket connection to 'wss://api.myserver.com/wss' failed: Unexpected response code: 302

If I test on a local server directly to the node instance https://localhost:3333/ it’s working perfectly and websockets work as they should.

Any idea of how to solve this? Is there a problem with ws redirections made by Apache proxy modules?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 7
  • Comments: 19 (5 by maintainers)

Most upvoted comments

Hi there! curiously enough, that fixed it 🎉. That extra “/” was really messing around with the Apache proxy configuration. I’m kind of pissed off of the lack of documentation on this existing on the internet.

Thank you very much for your support @lpinca, you really saved me from hours of banging my head against a wall. I was even thinking on dropping websockets altogether.

Thank you so very much!

@DigitalLeaves this super simple example works on my machine (no SSL)

$ httpd -v
Server version: Apache/2.4.23 (Fedora)
Server built:   Jul 18 2016 15:38:14
$ cat 00-proxy.conf 
# This file configures all the proxy modules:
LoadModule proxy_module modules/mod_proxy.so
LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_express_module modules/mod_proxy_express.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

Config:

<VirtualHost 192.168.0.30>
  ProxyPass /ws ws://192.168.0.197:3000/
  ProxyPassReverse /ws ws://192.168.0.197:3000/

  ProxyPass / http://192.168.0.197:3000/
  ProxyPassReverse / http://192.168.0.197:3000/
</VirtualHost>

The node server:

'use strict';

const WebSocket = require('ws');
const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  fs.createReadStream(__dirname + '/index.html').pipe(res);
});

const wss = new WebSocket.Server({ server });

server.listen(3000, () => console.log('listening on port 3000'));

index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <p>It works!</p>
    <script>
      (function () {
        var ws = new WebSocket('ws://' + location.host + '/ws');

        ws.onopen = function () {
          console.log('connected');
        };
      })();
    </script>
  </body>
</html>

Maybe it’s the trailing slash on /wss/ in your config?

ProxyPass /wss/ wss://localhost:3333/
<VirtualHost *:80>
  ServerName ws.serverlab.ca

  # Enable proxying for WebSocket connections
  RewriteEngine on
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule /(.*) ws://localhost:9000/$1 [P,L]

  # Reverse Proxy configuration
  ProxyPass / http://localhost:9000/
  ProxyPassReverse / http://localhost:9000/

  # Additional Apache proxy settings if required
  # ProxyRequests off
  # ProxyPreserveHost on
  # ProxyTimeout 300

</VirtualHost>

This works perfect

@nitin992503 yes, at the end it was a problem with the IP address, you need to pass the public IP through the path (I have no idea what you are doing), also I have to start apache on the server side

hello guys how are you?? i am having a similar error and i do not know what to do? Error during WebSocket handshake: Unexpected response code: 404, it does not connect to my app which is hosted at AWS - EC2 instance!! i would like to know if someone can help me?