Comfiguring Apache for socket.io and SSL / WSS - node.js

As the title suggests, I'm trying to get Apache and Socket.io (node.js) to play nicely, especially on SSL. Currently, the client app at https://www.example.com uses Socket.io over SSL to connect to the server at [SSL protocol]://socket.example.com/socket.io/?query_stuff*. The connection to wss:// always fails, so Socket.io degrades to https://, which works fine. But I would like to take advantage of the websocket protocol and not rely on polling over http(s).
Linux & Apache
Server version: Apache/2.4.7 (Ubuntu)
Relevant mods: mod_proxy, mod_proxy_http, mod_proxy_wstunnel, mod_rewrite, mod_ssl
Iptables: I have opened ports 80, 443, and 9000.
VirtualHost:
I created a virtualhost on *:443 called socket.example.com. It's intended purpose is to reverse proxy [wss,https]://socket.example.com/ to point to the socket.io server running at http://localhost:9000/. Here it is, with extraneous bits removed:
<VirtualHost *:443>
ServerName socket.example.com
DocumentRoot /var/www/example/socket.io/
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/socket.io [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*) wss://localhost:9000/$1 [P,L]
## I have also tried the following RewriteRules: ##
# RewriteRule /(.*) http://localhost:9000/$1 [P,L]
# RewriteRule /(.*) http://localhost:9000/socket.io/$1 [P,L]
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/www.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/keys/0001_key-certbot.pem
SSLCACertificateFile /etc/letsencrypt/ca-bundle.pem
SSLProxyEngine On
ProxyRequests Off
ProxyPreserveHost On
ProxyVia Full
ProxyPass / http://localhost:9000/
ProxyPassReverse / http://localhost:9000/
</VirtualHost>
Success using PHP websockets over SSL
Before switching to node.js for my websocket server, I used the above Apache VirtualHost to successfully route wss://socket.example.com/ws_daemon.php to ws://localhost:9000/ws_daemon.php. In this scenario I 1. removed the rewrite rules and 2. changed the ProxyPass settings to:
ProxyPass / ws://localhost:9000/
ProxyPassReverse / ws://localhost:9000/
But the same logic does not seem to carry over to socket.io.
At this point I've run out of ideas. Any help would be greatly appreciated!

If you are using the <Location> block, you should add the following lines to it:
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule /socket.io/(.*) ws://localhost:3000/socket.io/$1 [P]

Related

Rewrite rule not working with apache2, lucee tomcat

I have configured server with lucee tomcat and apache2 for virtual host on ubuntu. I have enabled rewrite rule and my virtual host is as followes.
<VirtualHost *:80>
ServerAdmin admin#example.com
ServerName example.com
ServerAlias example.com www.example.com
DocumentRoot /var/www/html/example.com/
<Directory /var/www/html/example.com/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error_main_example.log
CustomLog ${APACHE_LOG_DIR}/access_main_example.log combined
DirectoryIndex index.cfm
</VirtualHost>
redirect from htaccess file is working good but rewrite rule is not working. Here is the htaccess file that i am trying.
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [L,R=301]
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
RewriteRule ^(.*)/abc/([0-9]+)$ /test-404.cfm [L]
Here are the example URLs:
https://www.example.com/example.cfm/abc/2
https://www.example.com/example.cfm/abc/8
https://www.example.com/example.cfm/abc/15
It is showing me tomcat 404 error that
HTTP Status 404 – Not Found
Type Status Report
Message The requested resource [/example.cfm/abc/2] is not available
Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
Apache Tomcat/9.0.35
Can any body help me about this issue. By the way, site is configured using classic load balancer on AWS.
I'm posting a working solution here also, because you didn't answer to our posts of this cross post at the Lucee forum. This solution might also help others with the same problem.
The issue is that mod_proxy will always preced urlrewrite, unless you invoke mod_proxy with a special urlrewrite rule. To make urlrewrite work and use mod_proxy, you need to flag the rewrite rule with [P] (for proxy). Here is a working example that should work for you:
Step: Set everything in apache2.conf in mod_proxy.c as comment, just leaving ProxyPreserveHost and ProxyPassReverse, like so:
<IfModule mod_proxy.c>
ProxyPreserveHost On
#ProxyPassMatch ^/(.+\.cf[cm])(/.*)?$ http://127.0.0.1:8888/$1$2
#ProxyPassMatch ^/(.+\.cfml)(/.*)?$ http://127.0.0.1:8888/$1$2
# optional mappings
#ProxyPassMatch ^/flex2gateway/(.*)$ http://127.0.0.1:8888/flex2gateway/$1
#ProxyPassMatch ^/messagebroker/(.*)$ http://127.0.0.1:8888/messagebroker/$1
#ProxyPassMatch ^/flashservices/gateway(.*)$ http://127.0.0.1:8888/flashservices/gateway$1
#ProxyPassMatch ^/openamf/gateway/(.*)$ http://127.0.0.1:8888/openamf/gateway/$1
#ProxyPassMatch ^/rest/(.*)$ http://127.0.0.1:8888/rest/$1
ProxyPassReverse / http://127.0.0.1:8888/
</IfModule>
Step: In your virtual host configuration set the following rewrite rules ( that may work also in your .htaccess, but I’m not sure).
...
<Directory /var/www/html/example.com/>
...
...
RewriteEngine On
RewriteBase /
# Catch non-existing files/directories and pass them via proxy to a 404 cfml errorpage
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule "^(.*)$" "http://127.0.0.1:8888/my-404.cfm" [P]
# Catch https://www.example.com/example.cfm/abc/2
# Catch https://www.example.com/example.cfm/abc/7 etc.
RewriteRule "^example.cfm(/abc/[0-9]+)$" "http://127.0.0.1:8888/my-404.cfm" [P]
# Pass request for cfm/cfc files to proxy with mod_rewrite rule
RewriteRule "^(.+\.cf[cm])(/.*)?$" "http://127.0.0.1:8888/$1$2" [P]
...
</Directory>
I’ve tested it and the solution above works fine.

Config apache and nodejs socket.io

Hi I'm working on a website using apache 2.2 and feathersjs.
on this website we have DV SSL so we can't access subdomains using https.
my node application is running on 3030 port. so I can access to feathersjs app using this address:
http://mywebsite.com:3030
but https://mywebsite.com:3030 not working.
I need to use a Location like https://mywebsite.com/socket/ to connect to feathersjs websocket (guess ws://localhost:3030)
here is client side code:
const host = 'https://mywebsite.com/socket';
const socket = io(host,{
transports: ['websocket'],
forceNew: true
});
I need httpd configuration to connect ws over https.
post_virtualhost_global.conf
<VirtualHost 185.173.106.42:80>
ServerName mywebsite.com
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://localhost:3030%{REQUEST_URI} [P]
ProxyPass /socket http://localhost:3030/
ProxyPassReverse /socket http://localhost:3030/
ProxyRequests off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
</VirtualHost>
<VirtualHost 185.173.106.42:443>
ServerName freevery.com
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://localhost:3030%{REQUEST_URI} [P]
ProxyPass /socket http://localhost:3030/
ProxyPassReverse /socket http://localhost:3030/
ProxyRequests off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
<Location /feathers>
ProxyPass http://localhost:3030/
ProxyPassReverse http://localhost:3030/
</Location>
</VirtualHost>
if I try to connect web socket using http://mywebsite.com/socket it says
Failed to load
http://mywebsite.com/socket.io/?EIO=3&transport=polling&t=L-1lgZ1:
Redirect from
'http://mywebsite.com/socket.io/?EIO=3&transport=polling&t=L-1lgZ1' to
'https://mywebsie.com/socket.io/?EIO=3&transport=polling&t=L-1lgZ1'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin
'http://localhost:3001' is therefore not allowed access.
and if I try to connect web socket using https://mywebsite.com/socket it says
Failed to load
https://mywebsite.com/socket.io/?EIO=3&transport=polling&t=L-1lUP5: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3001' is therefore not allowed
access. The response had HTTP status code 404.
I added Header set Access-Control-Allow-Origin "*" to my .htaccess file but problem exists.
what is wrong with my configurations?
Problem solved. Apache had conflicts with nginx and 301 error was form it. So I used nginx to proxy websocket and it's working on a subdomain without any problem.

High Load on server due to websocket and Apache

We have 2 servers (Ubuntu 14.04 - 4vCPUs and 12 GB RAM), running a codeigniter application with Apache 2.4.7. These servers are load balanced as well. We have an average of 300 users accessing the site at a time and also the website has refresh functionality in many areas. So we introduced websockets to reduce the load. But even after introducing that, we are facing high load. Node is running on one of these server. After enabling mod_status I can see new connections are not getting opened for apache.
Vhost
<VirtualHost *:443>
ServerName domain.com
ServerAlias www.domain.com
DocumentRoot /var/www/html/domain
SSLEngine on
SSLProxyEngine On
SSLCertificateFile /etc/apache2/ssl/domain-ssl/a.crt
SSLCertificateKeyFile /etc/apache2/ssl/domain-ssl/domain.key
SSLCertificateChainFile /etc/apache2/ssl/domain-ssl/gd_bundle.crt
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/node/socket.io [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule "^/node/socket.io" "ws://IPADDRESS:8080/socket.io/" [P,L]
ProxyPreserveHost On
ProxyRequests off
</VirtualHost>
<Location /node/>
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
ProxyPass http://IPADDRESS:8080/ connectiontimeout=3 timeout=3 retry=0
ProxyPassReverse http://IPADDRESS:8080/
</Location>
Please let me know the details you need to investigate on this. Thanks
I installed a seperate server for node and redis which has reduced the load.

Apache2 Websocket Proxy for Mattermost from dynamic dns host

i'm using mattermost locally at home in a Vagrant Virtual Machine. With Port Forwarding on my DSL Router, i map the web frontend on a subomain on my WAN vHost with fixed IP.
<VirtualHost *:80>
ServerName chat.domain.tld
ServerSignature Off
ProxyPreserveHost On
<Location />
Order deny,allow
Allow from all
ProxyPassReverse http://chat.domain.tld/
</Location>
RewriteEngine on
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule .* http://mappedsubdomain.somedyndns.tld:8090%{REQUEST_URI} [P,QSA]
DocumentRoot /somewhere/on/my/disk
</VirtualHost>
And that works fine! In this case i'm mapped the Web Frontend from Port 8090 to the port 80 on the vHost Subdomain. And the Web-Frontend is reachable.
But.
mattermost is using another Port to communicate with the Web-Frontend over Websockets. For that i also forwarded the Websocket Port from my local Machine. If i'm accessing the Dynamic DNS Host Url: http://mappedsubdomain.somedyndns.tld:8090 the Web-Fontend works well WITH the second opened Port for Websockets. Mattermost is usable on the Dynamic DNS Host Url.
As Default Mattermost is using Port 80 for the Websockets. But in my case, i'm using port 890 for the Websockets in Mattermost. It works locally, inside the LAN and over the Dynamic DNS Host.
Now, i want to make a ProxyReverse withe the Websocket Protocol.
The WAN-Host is a Debian with Apache2.2 and the loaded mod_proxy_wstunnel Module.
At first, i tried simply to map the second Port:
Listen 890
<VirtualHost *:890>
ServerName chat.domain.tld
ServerSignature off
ProxyRequests off
RewriteEngine on
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://mappedsubdomain.somedyndns.tld:890/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://mappedsubdomain.somedyndns.tld:890/$1 [P,L]
<Location />
Order deny,allow
Allow from all
ProxyPassReverse http://mappedsubdomain.somedyndns.tld:890/
ProxyPassReverse ws://mappedsubdomain.somedyndns.tld:890/
</Location>
DocumentRoot /somewhere/on/my/disk
</VirtualHost>
But nothing. Websockets not working.
Then i tried it from a on the WAN vHost running NodeJS Websocket Tunnel:
https://www.npmjs.com/package/wstunnel
With this call:
wstunnel -t 8091 ws://mappedsubdomain.somedyndns.tld:890/
and with changed Virtual Host Config:
RewriteRule /(.*) ws://localhost:8091/$1
RewriteRule /(.*) http://localhost:8091/$1 [P,L]
ProxyPassReverse ws://localhost:8091/
ProxyPassReverse http://localhost:8091/
When wstunnel is running, a http Request on chat.domain.tld:890 ends with a timeout. Without wstunnel, i've got a 503.
Have anyone a helpful hint for me?
#seekwhencer Would this guide help? https://docs.mattermost.com/install/config-proxy-apache2.html
For troubleshooting WebSocket connections, this page might help: https://docs.mattermost.com/install/troubleshooting.html#please-check-connection-mattermost-unreachable-if-issue-persists-ask-administrator-to-check-websocket-port

WebSockets and Apache proxy: how to configure mod_proxy_wstunnel?

I have :
Apache 2.4 on port 80 of my server, with mod_proxy and mod_proxy_wstunnel enabled
Node.js + socket.io on port 3001 of the same server
Accessing example.com (with port 80) redirects to 2. thanks to this method with the following Apache configuration:
<VirtualHost *:80>
ServerName example.com
ProxyPass / http://localhost:3001/
ProxyPassReverse / http://localhost:3001/
ProxyPass / ws://localhost:3001/
ProxyPassReverse / ws://localhost:3001/
</VirtualHost>
It works for everything, except the websocket part : ws://... are not transmitted like it should by the proxy.
When I access the page on example.com, I have:
Impossible to connect ws://example.com/socket.io/?EIO=3&transport=websocket&sid=n30rqg9AEqZIk5c9AABN.
Question: How to make Apache proxy the WebSockets as well?
I finally managed to do it, thanks to this topic. TODO:
1) Have Apache 2.4 installed (doesn't work with 2.2), and do:
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
2) Have nodejs running on port 3001
3) Do this in the Apache config
<VirtualHost *:80>
ServerName example.com
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/socket.io [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*) ws://localhost:3001/$1 [P,L]
ProxyPass / http://localhost:3001/
ProxyPassReverse / http://localhost:3001/
</VirtualHost>
Note: if you have more than one service on the same server that uses websockets, you might want to do this to separate them.
Instead of filtering by URL, you can also filter by HTTP header. This configuration will work for any web applications that use websockets, also if they are not using socket.io:
<VirtualHost *:80>
ServerName www.domain2.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:3001/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://localhost:3001/$1 [P,L]
ProxyPassReverse / http://localhost:3001/
</VirtualHost>
May be will be useful.
Just all queries send via ws to node
<VirtualHost *:80>
ServerName www.domain2.com
<Location "/">
ProxyPass "ws://localhost:3001/"
</Location>
</VirtualHost>
As of Socket.IO 1.0 (May 2014), all connections begin with an HTTP polling request (more info here). That means that in addition to forwarding WebSocket traffic, you need to forward any transport=polling HTTP requests.
The solution below should redirect all socket traffic correctly, without redirecting any other traffic.
Enable the following Apache2 mods:
sudo a2enmod proxy rewrite proxy_http proxy_wstunnel
Use these settings in your *.conf file (e.g. /etc/apache2/sites-available/mysite.com.conf). I've included comments to explain each piece:
<VirtualHost *:80>
ServerName www.mydomain.com
# Enable the rewrite engine
# Requires: sudo a2enmod proxy rewrite proxy_http proxy_wstunnel
# In the rules/conds, [NC] means case-insensitve, [P] means proxy
RewriteEngine On
# socket.io 1.0+ starts all connections with an HTTP polling request
RewriteCond %{QUERY_STRING} transport=polling [NC]
RewriteRule /(.*) http://localhost:3001/$1 [P]
# When socket.io wants to initiate a WebSocket connection, it sends an
# "upgrade: websocket" request that should be transferred to ws://
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /(.*) ws://localhost:3001/$1 [P]
# OPTIONAL: Route all HTTP traffic at /node to port 3001
ProxyRequests Off
ProxyPass /node http://localhost:3001
ProxyPassReverse /node http://localhost:3001
</VirtualHost>
I've included an extra section for routing /node traffic that I find handy, see here for more info.
With help from these answers, I finally got reverse proxy for Node-RED running on a Raspberry Pi with Ubuntu Mate and Apache2 working, using this Apache2 site config:
<VirtualHost *:80>
ServerName nodered.domain.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:1880/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://localhost:1880/$1 [P,L]
</VirtualHost>
I also had to enable modules like this:
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
For me it works after adding only one line in httpd.conf as below (bold line).
<VirtualHost *:80>
ServerName: xxxxx
#ProxyPassReverse is not needed
ProxyPass /log4j ws://localhost:4711/logs
<VirtualHost *:80>
Apache version is 2.4.6 on CentOS.
Did the following for a spring application running static, rest and websocket content.
The Apache is used as Proxy and SSL Endpoint for the following URIs:
/app → static content
/api → REST API
/api/ws → websocket
Apache configuration
<VirtualHost *:80>
ServerName xxx.xxx.xxx
ProxyRequests Off
ProxyVia Off
ProxyPreserveHost On
<Proxy *>
Require all granted
</Proxy>
RewriteEngine On
# websocket
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/api/ws/(.*) ws://localhost:8080/api/ws/$1 [P,L]
# rest
ProxyPass /api http://localhost:8080/api
ProxyPassReverse /api http://localhost:8080/api
# static content
ProxyPass /app http://localhost:8080/app
ProxyPassReverse /app http://localhost:8080/app
</VirtualHost>
I use the same vHost config for the SSL configuration, no need to change anything proxy related.
Spring configuration
server.use-forward-headers: true
My setup:
Apache 2.4.10 (running off Debian)
Node.js (version 4.1.1) App running on port 3000 that accepts WebSockets at path /api/ws
As mentioned above by #Basj, make sure a2enmod proxy and ws_tunnel are enabled.
This is a screenshot of the Apache config file that solved my problem:
The relevant part as text:
<VirtualHost *:80>
ServerName *******
ServerAlias *******
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
<Location "/api/ws">
ProxyPass "ws://localhost:3000/api/ws"
</Location>
</VirtualHost>
Hope that helps.
In addition to the main answer: if you have more than one service on the same server that uses websockets, you might want to do this to separate them, by using a custom path (*):
Node server:
var io = require('socket.io')({ path: '/ws_website1'}).listen(server);
Client HTML:
<script src="/ws_website1/socket.io.js"></script>
...
<script>
var socket = io('', { path: '/ws_website1' });
...
Apache config:
RewriteEngine On
RewriteRule ^/website1(.*)$ http://localhost:3001$1 [P,L]
RewriteCond %{REQUEST_URI} ^/ws_website1 [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule ^(.*)$ ws://localhost:3001$1 [P,L]
RewriteCond %{REQUEST_URI} ^/ws_website1 [NC]
RewriteRule ^(.*)$ http://localhost:3001$1 [P,L]
(*) Note: using the default RewriteCond %{REQUEST_URI} ^/socket.io would not be specific to a website, and websockets requests would be mixed up between different websites!
User this link for perfact solution for ws https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html
You have to just do below step..
Go to /etc/apache2/mods-available
Step...1
Enable mode proxy_wstunnel.load by using below command
$a2enmod proxy_wstunnel.load
Step...2
Go to /etc/apache2/sites-available
and add below line in your .conf file inside virtual host
ProxyPass "/ws2/" "ws://localhost:8080/"
ProxyPass "/wss2/" "wss://localhost:8080/"
Note : 8080 mean your that your tomcat running port because we want to connect ws where our War file putted in tomcat and tomcat serve apache for ws.
thank you
My Configuration
ws://localhost/ws2/ALLCAD-Unifiedcommunication-1.0/chatserver?userid=4 =Connected
For "polling" transport.
Apache side:
<VirtualHost *:80>
ServerName mysite.com
DocumentRoot /my/path
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPass /my-connect-3001 http://127.0.0.1:3001/socket.io
ProxyPassReverse /my-connect-3001 http://127.0.0.1:3001/socket.io
</VirtualHost>
Client side:
var my_socket = new io.Manager(null, {
host: 'mysite.com',
path: '/my-connect-3001'
transports: ['polling'],
}).socket('/');
TODO:
Have Apache 2.4 installed (doesn't work with 2.2), a2enmod proxy and a2enmod proxy_wstunnel.load
Do this in the Apache config
just add two line in your file where 8080 is your tomcat running port
<VirtualHost *:80>
ProxyPass "/ws2/" "ws://localhost:8080/"
ProxyPass "/wss2/" "wss://localhost:8080/"
</VirtualHost *:80>
For the same issue on Windows, just uncomment the below line from http.conf:
Then add the below line to your apache config:
LoadModule proxy_module modules/mod_proxy_wstunnel.so

Resources