/etc/apache2/conf.d/userdata/std/2_4/ishaglcy/lookup.cafe/backend-proxy.conf
# Socket.IO with WebSocket support
<Location /socket.io>
ProxyPass http://127.0.0.1:5000/socket.io
ProxyPassReverse http://127.0.0.1:5000/socket.io
# WebSocket upgrade headers
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://127.0.0.1:5000/$1 [P,L]
# Forwarded headers
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
</Location>
# API endpoints
<Location /api>
ProxyPass http://127.0.0.1:5000/api
ProxyPassReverse http://127.0.0.1:5000/api
</Location>
# Health check
<Location /health>
ProxyPass http://127.0.0.1:5000/health
ProxyPassReverse http://127.0.0.1:5000/health
</Location>Production: Uses window.location.origin (automatically HTTPS)
- When accessed via
https://lookup.cafe, connects towss://lookup.cafe - When accessed via
http://localhost:5173, connects tohttp://localhost:5173
// src/App.tsx
initializeSocket({
url: import.meta.env.VITE_BACKEND_URL || window.location.origin,
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5,
});# .env (empty = use same origin)
VITE_BACKEND_URL=Browser (HTTPS)
↓
https://lookup.cafe
↓
Socket.IO connects to window.location.origin
↓
wss://lookup.cafe/socket.io/
↓
Apache (Port 443)
↓
Detects WebSocket Upgrade header
↓
Proxies to ws://127.0.0.1:5000/socket.io/
↓
Flask Backend (Port 5000)
↓
Socket.IO server handles connection
↓
Response flows back through proxy
↓
Browser receives via WSS (secure WebSocket)
| Access Method | Socket.IO Protocol | Proxied To |
|---|---|---|
https://lookup.cafe |
wss:// (WebSocket Secure) |
ws://127.0.0.1:5000 |
http://localhost:5173 (dev) |
ws:// (WebSocket) |
Direct connection |
curl -s "https://lookup.cafe/socket.io/?EIO=4&transport=polling"Expected: JSON response with session ID
curl -s https://lookup.cafe/api/rooms | jqExpected: {"rooms": []}
curl -s https://lookup.cafe/health | jqExpected: {"status": "ok", "database": "ok", "active_rooms": 0}
// Should see in console:
Socket connected: <socket-id>✅ Automatic Protocol - Client automatically uses WSS for HTTPS, WS for HTTP
✅ No Hardcoded URLs - Uses same origin, works on any domain
✅ SSL Termination - Apache handles HTTPS, backend stays HTTP
✅ WebSocket Upgrade - Properly configured for real-time communication
✅ Fallback Support - Socket.IO can use polling if WebSocket fails
✅ Development Friendly - Works on localhost without changes
Open browser DevTools → Network → WS tab
- Should see connection to
wss://lookup.cafe/socket.io/ - Status should be "101 Switching Protocols"
tail -f /tmp/lookup-backend.logLook for Socket.IO connection messages
apachectl -M | grep -E "proxy|rewrite"Required modules:
proxy_moduleproxy_http_moduleproxy_wstunnel_modulerewrite_module
/scripts/rebuildhttpdconf && /scripts/restartsrv_httpd- Apache proxy configured with WebSocket support
- Socket.IO client uses
window.location.origin - .htaccess excludes proxy paths
- Backend listening on 0.0.0.0:5000
- CORS configured for lookup.cafe
- Frontend rebuilt with new config
- HTTPS working via Apache
- WebSocket upgrade headers set
WebSocket connections were failing with "WebSocket connection to 'wss://lookup.cafe/socket.io/?EIO=4&transport=websocket' failed" error (400 BAD REQUEST).
The Apache proxy configuration had the RewriteRule inside the <Location> block, which doesn't work properly for WebSocket upgrade requests. The rewrite rules need to be inside a <LocationMatch> block or at the VirtualHost level for proper WebSocket handling.
Updated /etc/apache2/conf.d/userdata/std/2_4/ishaglcy/lookup.cafe/backend-proxy.conf to use <LocationMatch> with proper RewriteEngine directives that execute before ProxyPass:
<LocationMatch "^/socket\.io">
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
# Handle WebSocket upgrades
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/socket\.io/(.*)$ "ws://127.0.0.1:5000/socket.io/$1" [P,L]
# Regular HTTP proxy for polling
ProxyPass "http://127.0.0.1:5000/socket.io"
ProxyPassReverse "http://127.0.0.1:5000/socket.io"
</LocationMatch>This configuration:
- Checks for WebSocket upgrade headers
- Rewrites to
ws://protocol when WebSocket upgrade is detected - Falls through to regular HTTP proxy for Socket.IO polling transport
- Properly handles the full Socket.IO path including query parameters
After applying this configuration and reloading Apache, WebSocket connections should work correctly.
The WebSocket connection errors were caused by TWO issues:
- The .htaccess file was intercepting Socket.IO requests with a simple
[P,L]proxy rule that doesn't handle WebSocket upgrades - The backend was using
eventletasync_mode which had compatibility issues with proxied WebSocket connections
-
Updated .htaccess to include explicit WebSocket upgrade handling:
- Added separate RewriteRule for WebSocket upgrades (
ws://protocol) - Added separate RewriteRule for regular Socket.IO polling (HTTP)
- The WebSocket rule checks for
Upgrade: websocketandConnection: upgradeheaders
- Added separate RewriteRule for WebSocket upgrades (
-
Changed backend async_mode from
eventlettoNone(threading) inconfig.py:- Threading mode has better compatibility with reverse proxy setups
- More reliable WebSocket upgrade handling
WebSocket connections now successfully upgrade from HTTP to WebSocket protocol:
HTTP/1.1 101 Switching Protocols
Sec-WebSocket-Accept: ...
Connection: Upgrade
Upgrade: websocket
The application now properly uses WebSocket transport for real-time communication instead of falling back to long-polling.