I'm unable to make websockets work on a node backend using an apache proxy through HTTPS to connect to the node instance. 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 [email protected] 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?