WebSockets on shared hosting?

Is it possible to set up a Node.js WebSockets server using DreamHost shared hosting?

I’ve been successful in setting up a simple Node.js application using Passenger on shared hosting. However, when I tried to set up a simple chat app that uses WebSockets, I couldn’t get the WebSockets connection to open. This app is just a test to see if WebSockets are working, and the app works fine on my local machine, but I can’t rule out a dumb mistake. I know the code is running because I’m using “express-ws” and serving both a REST API and WebSockets, and the REST API is working fine, but any attempt to connect to WebSockets fails immediately.

I did some quick searching on Google, and I suspect it probably isn’t possible to set-up a WebSockets server on DreamHost shared hosting, but before I give up I thought I would check with the community to see if perhaps I’m wrong.

I think you’d need a VPS server or possibly higher to run WebSockets because it requires web-server configuration and a persistent service to run. I don’t think Apache on shared hosting has the mod_proxy_wstunnel module needed for WebSockets. Also, shared hosting limits how long a process can run, so WebSocket connections would get dropped periodically.

I managed to get a WebSocket server working on DreamHost shared hosting.

The code uses the (officially supported) Node.js via Passenger support from DreamHost to host the Node.js app and keep the persistent service running. The app opens a separate port to host the WebSockets server. I checked with DreamHost technical support and they were OK with this approach.

I published my code on GitHub here:

I hope others find this useful. To the best of my knowledge it complies with DreamHost TOS, but I take no responsibility if not.

Cool! I gather the websocket stays open as long as Passenger keeps the Node.js app running? Is that a long time, or does one need to “ping” on HTTP to keep it alive?

Passenger apps are persistent. If anything glitches out or you make changes you have to touch a home/[user]/[domain_dir]/tmp/restart.txt file in order to force your underlying apps to refresh/restart.

Ah, I had assumed it worked like FCGI (which shuts down after a few minutes of idle time). DH Passenger doc says “Passenger automatically launches applications and leaves them running as long as they are not idle”, but maybe that is out of date?

V8 runs scripts in a continuous loop and is never “idle” unless a process.kill directive is sent to terminate the PID (or a more abrupt, native process.exit() is encountered in a script/module).

Having said that, I guess they could monitor a process and kill it due to any reason they see fit – e.g. 0.0% CPU for 10 minutes or w/e.

One way to test might be to fire up a script that sends a singe message then does nothing and keep an eye on top for the node process to disappear – which I might actually try now :smiley:

I’m not 100% certain whether the app stays running or not. I have it hosted right now, and one observation is that if you visit the site after a long delay, the site takes much longer to load then if you revisit the site a few minutes later. I’m not sure if that is because the app needs to restart, or if the app is somehow “hibernated” while it is not in use. I don’t mind the slow start time, but what matters to me is if the data held in memory by the app persists across page loads.

I can do a test easily enough, I’ll add a variable that records the date/time when the app first starts, and add an endpoint that returns that date/time. I’ll force restart the app, wait a day, then load the page.

I’ll post back here with the results.

My test app ran for 2 hours “doing nothing” before node vanished from top.

Would be interesting to have it do something menial like overwrite a temporary file every 5 minutes to see if that keeps it “persistent”.

I did my test, and as expected (and as reported by sXi) the app appears to be killed after some period of time.

I added code at the beginning of the app to record the time when the app is started in a variable, and added a new URL that can be loaded to access the stored variable.

I tested the site, and when loading the site twice a few minutes apart, I got back the same date/time both times (i.e. the app persisted across both runs).

I checked again the next day, and the site took some time to start running, and once it did I got back the current time, so the app had restarted.

Thanks @chiron80 and @sXi for running the tests. It’s interesting to know how different the timeouts are for Passenger vs FCGI.

On the question of what “idle” means, I’ve always assumed it means “web idle” (as opposed to “CPU idle”, etc). I.e. no hits for a period of time. Like phone-systems of old, shared web-hosting is “oversubscribed”, so only a fraction of sites can be actively “talking on the phone” at any one time. So no user processes are allowed to run forever, because otherwise the host would be quickly overwhelmed.

I think a fair modern analogy is to say that shared web-hosting is a limited version of “Function as a Service” (FaaS). Like Lambda/Cloud-Functions/CF-Workers, it is “serverless” (i.e. a managed server) and you can run code (functions) for brief periods of time. However it is limited, because it can’t scale beyond one server, but that’s often fine for many uses.

I edited the test to do some menial tasks each minute (ping it’s own domain and append the timestamp to a log file) and left it to run for about 16 hours. Interestingly enough once it had tasks to perform node was killed and respawned every 12 minutes. Probably safe to assume that if session data is required to be persisted for longer than 5-10 minutes then a flatfile or database should be implemented.

By default, Passenger on DreamHost will restart your app every 400 requests, but you can adjust that threshold in a .htaccess file. This is in separate from whatever other non-.htaccess-configurable options such as PassengerPoolIdleTime that might be in play here.

PassengerMaxRequests 400

Should the .htaccess file live in the “public” folder or the same directory as the app.js file?

Same directory as app.js.