132

I have :

  1. Apache 2.4 on port 80 of my server, with mod_proxy and mod_proxy_wstunnel enabled

  2. 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?

Abdull
  • 26,371
  • 26
  • 130
  • 172
Basj
  • 41,386
  • 99
  • 383
  • 673

14 Answers14

199

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.

Basj
  • 41,386
  • 99
  • 383
  • 673
  • 1
    FWIW, Apache 2.4 in CentOS 7.1 has [this bug](https://bz.apache.org/bugzilla/show_bug.cgi?id=55598) therefore rewrite won't recognize ws:// protocol, and prepends the domain before sending the subrequest. You can test yourself by changing the [P]roxy flag to [R]edirect. – Andor Jul 23 '15 at 15:34
  • 3
    I still get 502 bad gateway for my ws:// routes when doing this. Running Apache 2.4 on Ubuntu 14.04 – Alex Muro Sep 10 '15 at 18:32
  • 2
    This works because all HTTP traffic is being forwarded as well, but if you only want to forward your socket traffic, please note that Socket.io starts communications with an HTTP polling request. More info [here](http://stackoverflow.com/a/41685748/4080966). – Erik Koopmans Jan 16 '17 at 23:05
  • 6
    How to forwarding from 443 wss to ws ? does rewritecond changes? – Hernán Eche Sep 12 '18 at 12:25
  • 2
    Condition `RewriteCond %{QUERY_STRING} transport=websocket [NC]` did not work me correctly. Suggesting to use `RewriteCond %{HTTP:Upgrade} =websocket [NC]` instead. – Martin Jul 05 '19 at 12:40
127

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>
cdauth
  • 6,171
  • 3
  • 41
  • 49
  • This is working fine for me but I'm proxying a subpath and I've found it's important to add the ^ anchors since the regex is looking for a substring. E.g. RewriteRule ^[^/]*/foo/(.*) http://${FOO_HOST}/$1 [P,L] (otherwise /bar/foo will also be directed to the same place as /foo) – Steve Lilly Sep 10 '16 at 09:50
  • This config work best on alibaba cloud. In addition, it will fix the error net::ERR_RESPONSE_HEADERS_TRUNCATED" Hope someone find this useful. – Mike Musni Aug 13 '19 at 01:30
  • Using SignalR, I can say, this worked best for me +1 Thanks – Vojtěch Mráz Jun 01 '20 at 21:48
24

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>
Sergey
  • 5,208
  • 25
  • 36
24

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.

  1. Enable the following Apache2 mods:

    sudo a2enmod proxy rewrite proxy_http proxy_wstunnel
    
  2. 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>
    
  3. I've included an extra section for routing /node traffic that I find handy, see here for more info.

Community
  • 1
  • 1
Erik Koopmans
  • 2,292
  • 2
  • 21
  • 30
  • 1
    This works perfectly for me. Note that if your requests are coming in to a URL other than root, you can do e.g. `RewriteRule /path/(.*)` – Rob Gwynn-Jones Nov 08 '18 at 23:15
10

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
Otto Paulsen
  • 334
  • 2
  • 11
10

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.

Leon
  • 3,124
  • 31
  • 36
9

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
Ortwin Angermeier
  • 5,957
  • 2
  • 34
  • 34
  • Use a Location tag to separate locations, i.e: ProxyPass http://localhost:8080/app ProxyPassReverse http://localhost:8080/app – Paul Allsopp Mar 12 '19 at 19:15
8

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:

Apache config

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.

Tsvetan Ganev
  • 8,246
  • 4
  • 26
  • 43
Anwaarullah
  • 227
  • 3
  • 7
  • I'm having trouble setting up my app with a prefered URL rather than the default, would you mind helping me out? (I'm sitting on top of a varnish cache server) – Josh Dec 30 '15 at 01:01
  • 6
    Could you copy/paste instead of screenshot? Thank you in advance, it would improve readability. – Basj Apr 06 '18 at 14:35
5

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!

Basj
  • 41,386
  • 99
  • 383
  • 673
3

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
Ikechukwu
  • 1,135
  • 1
  • 13
  • 30
  • Sorry I don't really understand (especially your last sentence). Can you improve formatting of the answer and add details for people who don't know all what you mention? – Basj Jun 28 '17 at 15:39
  • What is Tomcat @ArvindMadhukar? – Basj Jul 24 '18 at 18:42
1

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('/');
sNICkerssss
  • 6,312
  • 1
  • 24
  • 16
1

TODO:

  1. Have Apache 2.4 installed (doesn't work with 2.2), a2enmod proxy and a2enmod proxy_wstunnel.load

  2. 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>
    
legoscia
  • 39,593
  • 22
  • 116
  • 167
1

For the same issue on Windows, just uncomment the below line from http.conf:

enter image description here

Then add the below line to your apache config:

    LoadModule proxy_module modules/mod_proxy_wstunnel.so
1

First, activate required Apache modules:

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel

Then with the following template Apache reverse proxy site configuration (using the conventional host-port combination localhost:3000), socket.io websockets work (adapted from the official socket.io v4 documentation):

<VirtualHost *:80>
  # redirect anything HTTP-without-S to HTTPS-with-S
  ServerName websocket.example.com

  RewriteEngine On
  RewriteCond %{HTTPS} off
  RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
  # see https://httpd.apache.org/docs/2.4/rewrite/flags.html
  # see https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html#rewriterule

  ErrorLog ${APACHE_LOG_DIR}/websocket-http-error.log
  CustomLog ${APACHE_LOG_DIR}/websocket-http-access.log combined
</VirtualHost>

<VirtualHost *:443>
  ServerName websocket.example.com

  SSLEngine On
  SSLProxyCheckPeerName On

  # assuming let's encrypt certificate
  SSLCertificateFile /etc/letsencrypt/live/websocket/cert.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/websocket/privkey.pem
  SSLCertificateChainFile /etc/letsencrypt/live/websocket/chain.pem

  ProxyPass / http://localhost:3000/
  ProxyPassReverse / http://localhost:3000/

  # setup websocket socket.io configuration
  # see https://socket.io/docs/v4/reverse-proxy/#apache-httpd
  RewriteEngine on
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]

  ProxyTimeout 3

  ErrorLog ${APACHE_LOG_DIR}/websocket-https-error.log
  CustomLog ${APACHE_LOG_DIR}/websocket-https-access.log combined
</VirtualHost>
Abdull
  • 26,371
  • 26
  • 130
  • 172