4

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?

1 Answer 1

1

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 you X-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:

mode http option forwardfor frontend http-in bind :::80 v4v6 bind :::443 v4v6 ssl crt /etc/ssl/private http-request set-header X-Forwarded-Proto https if { ssl_fc } http-request set-header X-Forwarded-Port %[dst_port] acl is_websocket hdr(Upgrade) -i WebSocket acl is_websocket hdr_beg(Host) -i ws redirect scheme https code 301 if !is_websocket !{ ssl_fc } use_backend websocket_server if is_websocket backend websocket_server timeout queue 5s timeout server 86400s timeout connect 86400s server node_app 127.0.0.1:8080 backend ...others... 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.