How to Reverse Proxy Websockets with Apache 2.4

Overview

In this tutorial, you will learn how to configure Apache Web Server 2.4 to reverse proxy WebSockets.

WebSockets were introduced to open two-way interactive communication sessions, between a client and a server. This paved the way for event-driven responses, such as notifying a user of new content without refreshing the page.

Handling WebSockets in Apache Web Server 2.4 isn’t as straight forward as with other web servers. Performing a simple Google search of WebSocket problems with Apache, we can easily draw that conclusion.

The goal of this tutorial is to explain how to correctly configure Apache to reverse proxy WebSockets, using RewriteEngine and ProxyPass.

Getting Started

In order to enable WebSocket reverse proxying, the Apache modules for handling such requests must be enabled.

  • proxy
  • proxy_http
  • proxy_wstunnel

Ubuntu 16.04 / 18.04 / 19.04
If you are running Ubuntu 16.04 or higher, run the following commands to enable the modules.

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel

CentOS 7

The prerequisite modules are already enabled by default on a CentOS 7 install of httpd. Enabling and disabling these modules is done by editing a configuration file.

  1. Open the module configuration file for proxies.
    sudo vi /etc/httpd/conf.modules.d/00-proxy.conf
  2. All modules related to proxying are listed in this configuration file. Verify that the following lines exist and are uncommented.
    LoadModule proxy_http_module modules/mod_proxy_http.so
    LoadModule proxy_wstunnel modules/mod_proxy_wstunnel.so
  3. If you made any changes to the file, save them now.
  4. Restart Apache Web Server to apply your changes.
    sudo systemctl restart httpd

Configuring a WebSocket Reverse Proxy

The following is an example of a virtual host that supports web sockets. There are two important sections to configuration that you must understand.

The first section, where the rewrite module is used, must be done to convert it to the proper format for WebSockets. This is unique to Apache, as other web servers that support WebSockets will automatically perform this task for you.

The second section is where we reverse proxy the request to the backend servers.

<VirtualHost *:443>
  ServerName ws.serverlab.ca
  
  RewriteEngine on
  RewriteCond ${HTTP:Upgrade} websocket [NC]
  RewriteCond ${HTTP:Connection} upgrade [NC]
  RewriteRule .* "wss:/localhost:3000/$1" [P,L]
  
  ProxyPass / https://localhost:3000/
  ProxyPassReverse / https://localhost:3000/
  ProxyRequests off
</VirtualHost>

To understand exactly what has been configured above, a description of each directive and what it is doing is described below.

ServerName ws.serverlab.ca
The hostname of the virtual web host that will handle the WebSocket connections.

RewriteEngine on
Used to set the status of the
RewriteEngine to either on or off. To support WebSockets it must be turned on.

RewriteCond ${HTTP:Upgrade} websocket [NC]
A condition that must be matched in order for a request to be processed by the RewriteRule.

RewriteCond ${HTTP:Connection} upgrade [NC]
To something

RewriteRule .* “wss:/ws-backend%{REQUEST_URI}” [P]
Rewrite all incoming requests to use the wss protocol, and replace the destination hostname to that of a backend service.

Reverse Proxy Load Balancing

The example given above is used when both your backend application and the Apache Proxy server are running on the same host.

When a reverse proxy needs to send traffic to multiple backend servers, the proxy Load Balancer module is used. With the load balancer module, we define a pool of backend servers, and Apache will send the request using a defined balancing algorithm.

To expand on the first example, we will modify it to include the load balancer directive.

<VirtualHost *:443>
  ServerName ws.serverlab.ca
  
  RewriteEngine on
  RewriteCond ${HTTP:Upgrade} websocket [NC]
  RewriteCond ${HTTP:Connection} upgrade [NC]
  RewriteRule .* "wss:/localhost:3000/$1" [P,L]
  
  <Proxy balancer://backend-cluster>
    BalancerMember http://server01:3000
    BalancerMember http://server02:3000
    BalancerMember http://server03:3000
  </Proxy>

  ProxyPass / balancer://backend-cluster/
  ProxyPassReverse / balancer://backend-cluster/
  ProxyRequests off
</VirtualHost>