Quick answer: The signaling connection usually fails because the default signaling server is unavailable, you are mixing HTTP/HTTPS with WS/WSS, or firewall/NAT rules are blocking WebSocket traffic. Use a self-hosted signaling server with WSS, configure STUN and TURN ICE servers for NAT traversal, and add error handlers to diagnose the specific failure point.
You built a multiplayer game in Construct 3 using the Multiplayer plugin. It worked in local testing. Then you exported it, hosted it online, and players cannot connect to each other. The signaling step fails, peers never discover each other, and the game either shows a connection error or silently times out. Multiplayer networking has several failure points, and the signaling server is the first one. Let us work through each possible cause.
The Symptom
When your game tries to connect to the signaling server using the Multiplayer object’s Connect action, nothing happens. The On connected to signaling server trigger never fires. Instead, you may see On signaling error fire, or no event fires at all and the connection attempt silently times out after 10–30 seconds.
If signaling does connect but peers cannot find each other, you might see the host successfully create a room but joining peers get On join error or time out while trying to join. In the browser console, you may see WebSocket errors like WebSocket connection to 'wss://...' failed or Mixed Content: The page was loaded over HTTPS but attempted to connect to an insecure WebSocket endpoint.
In some cases, the signaling connection works but the actual peer-to-peer data channel fails to establish. Both players appear to be in the room, but no data flows between them. This manifests as On peer connected never firing, or peers appearing connected but On peer message never triggering.
What Causes This
There are five distinct failure points in Construct 3’s multiplayer connection pipeline:
1. The signaling server is unavailable or deprecated. Scirra provided a default signaling server for development purposes, but it is not guaranteed to be available for production use. If the default server is down, overloaded, or has been deprecated, your game cannot establish initial contact between peers. The signaling server is only used for the initial handshake — once peers discover each other, data flows directly between them via WebRTC — but without that initial handshake, nothing works.
2. Mixed content: HTTPS page connecting to WS (non-secure) WebSocket. Modern browsers block insecure WebSocket connections (ws://) from pages served over HTTPS. If your game is hosted on an HTTPS domain but your signaling server URL starts with ws:// instead of wss://, the browser will silently block the connection. This is the most common cause for games that work in local preview (HTTP) but fail when deployed (HTTPS).
3. Firewall or corporate network blocking WebSocket connections. Some firewalls, particularly on corporate, school, and public Wi-Fi networks, block WebSocket connections on non-standard ports. Even if your signaling server is running, if it is on a port like 4533 and the player’s firewall only allows traffic on ports 80 and 443, the connection will fail.
4. NAT traversal failure — no TURN server configured. After signaling succeeds, peers try to establish a direct WebRTC connection. This requires NAT traversal via ICE (Interactive Connectivity Establishment). STUN servers help peers discover their public IP addresses, but if both peers are behind symmetric NATs (common on mobile networks and some routers), STUN alone is not enough. A TURN server is needed to relay traffic, but Construct 3 does not include one by default.
5. Incorrect ICE server configuration. If you have configured custom ICE servers but the URLs, usernames, or credentials are wrong, ICE candidate gathering will fail. You may see On ICE candidate error events, or the peer connection will silently time out after gathering only host candidates (local network IPs that are useless for internet connections).
The Fix
Step 1: Set up a reliable signaling server URL.
First, test whether the signaling server is reachable. You can check from the browser console:
// Test signaling server connectivity from browser console:
const ws = new WebSocket("wss://your-signaling-server.example.com");
ws.addEventListener("open", () => console.log("Connected!"));
ws.addEventListener("error", (e) => console.log("Failed:", e));
// If this fails, the signaling server is unreachable.
// If it connects, the server is fine and the issue is elsewhere.
In your Construct 3 project, set the signaling server URL in the Multiplayer object’s Connect action:
// Connect to the signaling server:
Event: On button "Connect" clicked
Action: Multiplayer > Connect
Server: "wss://your-signaling-server.example.com"
// ALWAYS use wss:// (not ws://) for HTTPS-hosted games.
// ws:// will be blocked by the browser's mixed content policy.
Step 2: Self-host a signaling server with SSL.
For production games, run your own signaling server. Scirra provides an open-source Node.js signaling server:
# On your server (VPS, cloud instance, etc.):
# 1. Install Node.js (18+ recommended)
# 2. Clone the signaling server
git clone https://github.com/AshleyScirra/construct-3-signalling-server.git
cd construct-3-signalling-server
npm install
# 3. Set up SSL certificates (Let's Encrypt recommended)
# Use certbot to generate certificates for your domain
# 4. Run the server on port 443 for maximum firewall compatibility
node signal.js --port 443 \
--ssl-cert /etc/letsencrypt/live/signal.yourgame.com/fullchain.pem \
--ssl-key /etc/letsencrypt/live/signal.yourgame.com/privkey.pem
# Alternative: use a reverse proxy (nginx) for SSL termination
# nginx config:
# location /signal/ {
# proxy_pass http://localhost:4533;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# }
# Then use: wss://yourgame.com/signal/ in Construct 3
Step 3: Configure ICE servers with STUN and TURN.
Add ICE server configuration so peers can traverse NATs and firewalls:
// In your Construct 3 event sheet, add ICE servers
// BEFORE connecting to the signaling server:
Event: On start of layout
// Add a free public STUN server:
Action: Multiplayer > Add ICE server
URL: "stun:stun.l.google.com:19302"
// Add a second STUN server for redundancy:
Action: Multiplayer > Add ICE server
URL: "stun:stun1.l.google.com:19302"
// Add a TURN server for players behind symmetric NAT:
Action: Multiplayer > Add ICE server
URL: "turn:turn.yourgame.com:443"
Username: "your-username"
Credential: "your-credential"
// Without TURN, ~10-15% of players cannot connect.
// STUN alone handles most cases but fails for:
// - Symmetric NAT (many mobile carriers)
// - Restrictive corporate firewalls
// - Some hotel/airport WiFi networks
Step 4: Add comprehensive error handlers.
Without error handlers, connection failures are silent. Add handlers for every failure event:
// Signaling connection errors:
Event: Multiplayer > On signaling error
Action: ErrorText > Set text
"Signaling error: " & Multiplayer.ErrorMessage
Action: System > Log
"Signaling failed: " & Multiplayer.ErrorMessage
// Room join errors:
Event: Multiplayer > On join error
Action: ErrorText > Set text
"Join error: " & Multiplayer.ErrorMessage
// Peer connection errors:
Event: Multiplayer > On peer error
Action: System > Log
"Peer error for " & Multiplayer.PeerAlias
& ": " & Multiplayer.ErrorMessage
// ICE candidate errors (NAT traversal failures):
Event: Multiplayer > On ICE candidate error
Action: System > Log
"ICE error: " & Multiplayer.ErrorMessage
Step 5: Implement a connection retry with timeout.
// Retry logic for signaling connection:
Global variable RetryCount = 0
Global variable MaxRetries = 3
Event: On function "TryConnect"
Action: System > Add 1 to RetryCount
Action: StatusText > Set text
"Connecting (attempt " & RetryCount & "/" & MaxRetries & ")"
Action: Multiplayer > Connect
Server: "wss://signal.yourgame.com"
Event: Multiplayer > On signaling error
Condition: RetryCount < MaxRetries
Action: System > Wait 2 seconds
Action: Functions > Call "TryConnect"
Event: Multiplayer > On signaling error
Condition: RetryCount >= MaxRetries
Action: ErrorText > Set text
"Could not connect. Check your internet connection."
Step 6: Use port 443 for maximum firewall compatibility.
// Many firewalls only allow traffic on ports 80 and 443.
// Run your signaling server on port 443 with SSL,
// or use a reverse proxy on port 443 to forward to it.
//
// Also configure your TURN server on port 443:
// turn:turn.yourgame.com:443
//
// This maximizes the chance that both signaling and
// TURN traffic can pass through restrictive networks.
//
// For TURN, port 443 with TCP transport is the most
// firewall-friendly configuration:
// turn:turn.yourgame.com:443?transport=tcp
Why This Works
Construct 3’s multiplayer system uses WebRTC for peer-to-peer communication, with a WebSocket-based signaling server for the initial handshake. The connection pipeline has three stages: signaling (WebSocket), ICE negotiation (STUN/TURN), and data channel establishment (WebRTC). Each stage has different failure modes.
The signaling server acts as an introduction service. It does not relay game data — it only helps peers discover each other’s network addresses and exchange connection offers. Once peers have each other’s information, they disconnect from the signaling server and communicate directly. This is why the signaling server does not need much bandwidth or processing power, but it must be reachable by all players.
WSS (WebSocket Secure) is required because modern browsers enforce strict mixed-content policies. An HTTPS page cannot open a non-secure WebSocket connection. Since most game hosting platforms serve over HTTPS, your signaling server must support TLS. This is non-negotiable for deployed games.
TURN servers solve the “last mile” problem. When two peers are both behind NATs that do not support the standard STUN hole-punching technique, the only option is to relay traffic through a server that both peers can reach. TURN is that relay. It adds latency (since data goes client → TURN → client instead of client → client), but it ensures connectivity for the ~10–15% of players who would otherwise be unable to connect.
“Multiplayer that works on localhost is not multiplayer. Test with real devices on different networks before you ship.”
Related Issues
If your multiplayer game connects but state desyncs between players after layout transitions, check global variable resets on layout restart to ensure game state persists correctly. If the game crashes during multiplayer sessions with GPU errors, see WebGL context lost errors for memory management strategies. If players get stuck on the loading screen and time out before connecting, loader layout stuck not progressing covers preloading issues. And if multiplayer objects render in the wrong visual order, Z order not updating at runtime explains how to manage rendering with dynamically created peer objects.
WSS, not WS. Every time. No exceptions.