Socket.io with Apache on a CPanel VPS
Tags: webdev, node, linux, angular
Published: 2020-06-30 23:45:58

I've been running Node.js apps on my WHM/CPanel VPS for a little over a year now and proxying web requests through Apache has been working pretty well. 

However, with my latest project, I'm using Socket.io to send messages to the front-end in real-time. I had everything working locally, but after deploying both the API and my front-end, the websockets were unable to connect.

After a bit of reading, it became clear that I need to use `ProxyPass` which isn't allowed in a .htaccess file. On a typical deployment you'd add it to a configuration file under the `sites-available` folder, but with WHM/CPanel, that option just isn't there.

On a cPanel/WHM server, the Apache configuration file is an `httpd.conf` that gets re-generated each time changes are made, so it's *really* not recommended to make changes to it manually. 

Here's the notice at the bottom of the file:
```
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
#   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#   DO NOT EDIT. AUTOMATICALLY GENERATED.  USE INCLUDE FILES IF YOU NEED TO MAKE A CHANGE
#   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
```
That doesn't mean we can't override to the VirtualHost settings, we just need to make our changes in an include file.

Have a look at your httpd.conf by printing it to the terminal with:
```
$ cat /etc/httpd/conf/httpd.conf
```

Note the following lines after each VirtualHost, where `myuser` and `mydomain.net` match your CPanel account.
```
# To customize this VirtualHost use an include file at the following location
# Include "/etc/apache2/conf.d/userdata/ssl/2_4/myuser/api.mydomain.net/*.conf"
```

> Notice that the path above includes ".../ssl/...". 
> There's a corresponding ".../std/..." for services that
> don't require a secure connection. I won't recommend
> running any services without SSL, so I didn't even look
> at that configuration.

The folder listed above won't likely exist yet, but you can create it with:
```bash
$ mkdir -p /etc/apache2/conf.d/userdata/ssl/2_4/myuser/api.mydomain.net
```

Then create a new config file with:
```bash
$ cd /etc/apache2/conf.d/userdata/ssl/2_4/myuser/api.mydomain.net
$ touch custom.conf
$ nano custom.conf
```
The contents of the custom configuration will vary based on your need, but in my case, I needed to connect to a socket host running on port 7222 on the `api.mydomain.net` subdomain. The same subdomain also hosts my Node.js REST api, so I needed to split out the websocket traffic and upgrade it as needed.

> FYI: I wasted a lot of time editing my `custom.conf` file in the domain root ".../myuser/mydomain.net", where it should have been in the folder that corresponds to my subdomain ".../myuser/**api.**mydomain.net".
> It was a huge waste of time as I fiddled with settings that were not applied to the subdomain where I was testing my application. If you're making edits that don't appear to have any effect, confirm that your config file is in the folder for the domain you're testing against.

In broad strokes, I need to proxy any websocket traffic to localhost:7222. The contents of my `custom.conf` file is as follows:
```
ProxyPreserveHost On
RewriteEngine On

# Upgrade connections to WebSockets
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:7222/$1 [P,L]

# Redirect http to https
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Everything else forwards as HTTP to the node app.
ProxyPass / http://127.0.0.1:7222/
ProxyPassReverse / http://127.0.0.1:7222/
```

Once I felt that the rules were correct I saved the file and ran:
```
$ /scripts/verify_vhost_includes
> Testing
> /etc/apache2/conf.d/userdata/ssl/2_4/myuser/api.mydomain.net/custom.conf...ok
```
This confirmed that my changes weren't broken so badly that they'd prevent Apache from trying to load them. If that were to happen, and I didn't test it first, I could potentially prevent other websites on my host from being served.

This is only one of two VirtualHosts with a custom config file on my machine so I was happy to make sure we use all the existing include files:
```
$ /scripts/ensure_vhost_includes --all-users
> Built /etc/apache2/conf/httpd.conf OK
```

Alternatively, I could have used the following CPanel script to ensure that my `httpd.conf` contained the necessary `Include` statements.

```
$ /usr/local/cpanel/scripts/rebuildhttpdconf
> Built /etc/apache2/conf/httpd.conf OK
```

The only remaining step was to bounce Apache & test out my new VirtualHost Rewrite & ProxyPass rules.


To restart Apache, run the following:
```
$ /scripts/restartsrv_httpd
```
This should do a soft restart, which simply means that it will restart after it finishes serving any active connections.

After making these changes, my Angular app socket service was able to successfully connect to a Node.js app running behind my Apache webserver.