Quick answer: For a P2P connection the host must create a listen socket on the same virtual port the client connects to, and must call AcceptConnection when the connection status callback reports Connecting. Initialize relay access early with InitRelayNetworkAccess(), run callbacks every frame, log the end reason and debug string, and test with two different Steam accounts.
Steam Networking Sockets routes P2P traffic through Valve's relay network, so NAT and firewalls are rarely the problem. When ConnectP2P fails, it is almost always the handshake: the host never accepted, the ports do not match, or callbacks never ran.
- How a P2P connection is supposed to go
- Cause 1: the host never accepts
- Cause 2: virtual port mismatch
- Cause 3: relay network not ready
- Cause 4: callbacks or receives not running
- Cause 5: testing with one account
- Turn on the library’s own logging
- Engine integrations
- Disconnects in the wild
- Related problems
- Frequently asked questions
How a P2P connection is supposed to go
- The host calls
CreateListenSocketP2P(virtualPort, ...). - The client calls
ConnectP2P(hostIdentity, virtualPort, ...). - The host receives
SteamNetConnectionStatusChangedCallback_twith stateConnectingand callsAcceptConnection. - Both sides receive a status change to
Connected.
Each step depends on callbacks being processed. Miss one and the client sits in Connecting until it times out.
Cause 1: the host never accepts
void Net::OnStatusChanged(SteamNetConnectionStatusChangedCallback_t* info)
{
switch (info->m_info.m_eState)
{
case k_ESteamNetworkingConnectionState_Connecting:
if (info->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
SteamNetworkingSockets()->AcceptConnection(info->m_hConn);
break;
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
printf("Connection ended: %d %s\n", info->m_info.m_eEndReason, info->m_info.m_szEndDebug);
SteamNetworkingSockets()->CloseConnection(info->m_hConn, 0, nullptr, false);
break;
default: break;
}
}
Only incoming connections on a listen socket have m_hListenSocket set; outgoing connections from the client should not be accepted.
Cause 2: virtual port mismatch
The virtual port is an application-level channel number, not a UDP port. Host and client must use the same value. A client connecting on port 0 to a host listening on port 1 never reaches it. Define it once as a constant shared by both sides.
Cause 3: relay network not ready
Connections go through Steam Datagram Relay, which needs to download network configuration and measure ping to relays. Call this right after Steam initializes, long before the first connection:
SteamNetworkingUtils()->InitRelayNetworkAccess();
Without it, the first connection attempt has to do that work and can time out on slow networks. You can poll GetRelayNetworkStatus to show “connecting to Steam network” in your UI.
Cause 4: callbacks or receives not running
Run SteamAPI_RunCallbacks() every frame on both sides, and poll ReceiveMessagesOnConnection (or a poll group) regularly. A host that only processes callbacks while in a menu stops accepting connections once gameplay starts.
Cause 5: testing with one account
The identity you connect to is a Steam account. Two copies of the game on one PC, or two PCs logged into the same account, cannot form a meaningful P2P connection. Use two accounts.
Turn on the library’s own logging
static void NetDebug(ESteamNetworkingSocketsDebugOutputType type, const char* msg)
{
printf("[SteamNet] %s\n", msg);
}
SteamNetworkingUtils()->SetDebugOutputFunction(k_ESteamNetworkingSocketsDebugOutputType_Msg, NetDebug);
The output explains relay selection, timeouts and rejections in plain text. Combined with m_szEndDebug from the status callback, it usually names the exact failure.
Engine integrations
Unreal's Steam Sockets net driver, GodotSteam's networking functions and Unity transports for Mirror, FishNet and Netcode for GameObjects all sit on the same API, so the same causes apply: listen on the host, matching ports, callbacks running and two accounts.
Disconnects in the wild
Connection failures only show their real causes on real networks. Bugnet's SDKs for Unity, Godot and Unreal file errors automatically with the player's OS, hardware and recent log lines, and pull your Steam reviews into the same dashboard. Log the end reason and debug string as an error and every failed connection arrives grouped, with the build and platform.
Related problems
- Fix: Steam Lobby List Returns No Lobbies (RequestLobbyList Empty)
- How to Fix Steam Friend Invites and Join-Game Not Working
- How to Fix a Steam Authentication Session Ticket Being Rejected
- Signs Your Game Has a Desync Problem
Frequently asked questions
Why does ConnectP2P never connect in Steam Networking Sockets?
Usually the host never calls AcceptConnection on the Connecting status callback, the virtual ports differ, callbacks are not running, or both sides use the same Steam account.
Do I need to open ports for Steam P2P?
Normally no. Steam Networking Sockets relays traffic through Steam Datagram Relay, so NAT and firewalls are rarely the cause.
How do I see why a Steam connection closed?
Read m_eEndReason and m_szEndDebug from the connection status callback, and enable SetDebugOutputFunction for detailed library logs.
Listen, connect on the same port, accept on Connecting. Most P2P bugs are a missing AcceptConnection.