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?
You've already set up HTTPS on Apache so you've got a secure connection from the client to your physical server. You're proxying the connection from Apache to your node.js app on localhost, so the only possible eavesdroppers are physically on your box already. You've told your app to trust the proxy with
app.enable('trust proxy'); app.set("trust proxy", 1);
So why would you also configure HTTPS for node.js? One of the main reasons we front-end our web apps with apache/nginx/haproxy is TLS offloading.
In addition to getting the
X-Forwarded-For
header with the remote clients IP, enabling proxy trust also gets youX-Forwarded-Proto
so your Express app knows if the connection was http or https. Therefore, the https layer between Apache and node is doing little besides introducing overhead for your app.I can't help you with proxying WebSockets with Apache. I started using Apache back when it was still a series of patches for httpd. I quit before WebSockets rolled around and I can't risk falling off the wagon. Too many painful memories. If you're curious, here's how I use haproxy to pass WebSockets to one of my express apps: