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?