How to Configure Apache mod_proxy to Forward WSS to WS for PHP WebSocket Backends


10 views

When working with legacy PHP WebSocket implementations like PHP-Websockets, you might encounter SSL termination issues. Modern browsers require secure WebSocket connections (wss://) when the parent page is served via HTTPS, but your backend may only support insecure WS protocol.

First, verify you have these Apache 2.4+ modules loaded:


LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule ssl_module modules/mod_ssl.so

The critical module often missed is mod_proxy_wstunnel - the WebSocket-specific proxy handler.

Here's a complete working configuration for SSL termination:


<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem

    # WebSocket proxy configuration
    ProxyPass "/_ws_/" "ws://localhost:8080/" upgrade=websocket
    ProxyPassReverse "/_ws_/" "ws://localhost:8080/"
    
    # Required headers for WebSocket
    RequestHeader set Connection "upgrade"
    RequestHeader set Upgrade "websocket"
</VirtualHost>

If you encounter the "No protocol handler" error, check:


# Check loaded modules
apachectl -M | grep -E 'proxy|ssl'

# Verify the syntax
apachectl configtest

For those using Nginx as reverse proxy:


location /_ws_/ {
    proxy_pass http://localhost:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

The JavaScript implementation remains standard:


const socket = new WebSocket('wss://example.com/_ws_/');
socket.onopen = function(e) {
    console.log("Connection established");
};

When proxying WebSocket connections:

  • Always use WSS for production
  • Implement origin verification
  • Consider rate limiting
  • Use secure WebSocket libraries when possible

When working with the PHP-Websockets library, you might encounter a limitation: it doesn't support secure WebSocket connections (wss). This becomes problematic when your website is served over HTTPS, as browsers will block insecure ws connections on secure pages.

To solve this, we need to configure Apache's mod_proxy to forward secure WebSocket requests (wss) to an insecure WebSocket server (ws). First, ensure you have the necessary modules loaded:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule ssl_module modules/mod_ssl.so

The error message "No protocol handler was valid for the URL" typically indicates that Apache doesn't know how to handle WebSocket connections. The solution is to enable mod_proxy_wstunnel, which provides WebSocket protocol support.

Here's a working configuration for your Apache VirtualHost:

<VirtualHost *:443>
    ServerName example.com
    
    SSLEngine on
    SSLCertificateFile /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem
    
    ProxyPass "/_ws_/" "ws://127.0.0.1:8080/" upgrade=websocket
    ProxyPassReverse "/_ws_/" "ws://127.0.0.1:8080/" upgrade=websocket
    
    # Required headers for WebSocket proxying
    ProxySet "/_ws_/" upgrade=websocket
    RequestHeader set Upgrade "websocket"
    RequestHeader set Connection "upgrade"
</VirtualHost>

Your client-side JavaScript remains the same, but now it will work properly with HTTPS:

var ws = new WebSocket('wss://example.com/_ws_/');
ws.onopen = function() {
    console.log('Connection established!');
};

If you still encounter issues:

  1. Verify all required modules are loaded (apachectl -M)
  2. Check Apache error logs for detailed messages
  3. Test the WebSocket server directly with ws:// to ensure it's running
  4. Use browser developer tools to inspect the WebSocket handshake

If you're open to switching web servers, Nginx has excellent WebSocket proxying support:

location /_ws_/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}