Troubleshooting

Why Is P2P File Transfer Slow? How to Find the Bottleneck

By SRIFT maintainers · Published

Reviewed by SRIFT maintainers · last reviewed against the SRIFT 4.3.2 source · third-party facts checked 8 October 2026.

Short answer

A peer-to-peer transfer runs at the speed of its slowest stage: reading the file, encrypting and chunking it, the sender's upload, the network path, the receiver's download, and writing to disk. On home and mobile connections the usual limit is the sender's upload bandwidth, which is often much lower than its download. The next suspects are a relayed path (TURN or a WebSocket relay), Wi-Fi, a VPN, and endpoint problems such as a throttled background tab or a slow disk.

Guessing wastes time. Measure each stage on its own, check which path the connection actually took, and fix only the narrowest one.

Usual cap
The sender's upload bandwidth
Where to look
chrome://webrtc-internals or about:webrtc: the selected candidate pair
Rarely the cap
AES-GCM on a desktop CPU with hardware AES
Often overlooked
Background-tab throttling and too little data in flight

A transfer is a pipeline: the slowest stage sets the speed

Every byte passes through the same sequence of stages, and each stage has its own capacity. Throughput is the minimum of those capacities, not their average. A gigabit fibre download on the receiving side does nothing if the sender is uploading over a connection with a fraction of that, and a fast network does nothing if the receiving phone writes to storage slowly or the sending tab has been throttled in the background.

A file transfer as a pipe whose narrowest stage sets the speedSix stages from left to right: reading the file, encryption and JavaScript, the sender's upload, the network path, the receiver's download, and writing to disk. Each stage is drawn as a pipe segment of a different height. The sender's upload is the narrowest segment, so it limits the whole transfer.Read filedisk, memoryEncrypt, JSCPU, chunkingSender uploadPathdirect / TURN / relayReceiverdownload, Wi-FiWritedisk, browser storagenarrowest stage = transfer speedwidening any other stage changes nothingSegment heights are illustrative, not measurements
The narrowest stage limits the whole transfer. On consumer connections that is usually the sender's upload.

This is why “remove the server and it gets faster” is only sometimes true. A direct path removes a hop, but if the hop was not the narrowest stage, nothing changes. The method in this article is simple: estimate the capacity of each stage, find the smallest, and only then change something.

Network limits: upload, download, Wi-Fi and VPNs

The sender's uplink

Many residential and mobile plans are asymmetric: upload is a fraction of download. A peer-to-peer transfer is an upload from the sender, so run a speed test on the sending device and note the upload figure. If the transfer runs close to it, you have found the cap, and no software setting will raise it. Two practical fixes: send from the better-connected machine (an office or a cloud VM rather than a phone on cellular), or move the sending device to a wired connection.

The receiver's downlink

Less often the limit, but check it on mobile data, on congested shared networks, and when someone else on the receiving network is downloading heavily. Both tests matter because the transfer gets the lower of the two.

Wi-Fi contention and distance

The link rate your operating system displays is a signal-quality estimate, not throughput. Wi-Fi is a shared medium: every device on the channel, and neighbouring networks on the same channel, compete for airtime, and the rate drops as signal weakens with distance and walls. When both devices are on the same access point, each packet crosses the air twice (sender to access point, access point to receiver), so a same-room transfer can be slower than either device's speed test suggests. Wiring one side, moving closer, or using the 5 GHz or 6 GHz band usually helps more than any software change.

VPNs

A VPN adds encapsulation overhead and sends traffic through its own server, which may be far from both peers. Some VPN clients also block UDP outside the tunnel, which pushes WebRTC from a direct path onto a TURN relay or stops it from connecting at all (see why WebRTC connections fail). If a transfer is slow only with the VPN on, test with it off or with split tunnelling for the browser.

Which path did the connection take?

A WebRTC data channel can run over three kinds of path, and a relay-based tool adds a fourth:

  • Direct, host or server-reflexive candidates. Packets go from one peer's network to the other's. The fastest option when it works.
  • TURN over UDP. Traffic goes through a TURN server (RFC 8656). It adds a hop, the round-trip time to that server, and the server's own capacity, but keeps UDP semantics.
  • TURN over TCP or TLS. Used when UDP is blocked. The data channel's SCTP now runs inside DTLS inside TCP, so a single lost packet stalls everything behind it (head-of-line blocking) and two congestion controllers react to the same loss. Expect it to suffer most on lossy networks.
  • An application relay over WebSockets. No WebRTC at all: both peers hold TCP connections to a server that forwards chunks. Speed depends on each side's route to that server and on how the application frames data. Sending binary inside JSON as base64 inflates every chunk by a third (RFC 4648 encodes 3 bytes as 4 characters) and costs CPU to encode and decode.

Reading the selected candidate pair

In Chrome or Edge, open chrome://webrtc-internals (or edge://webrtc-internals) in a new tab before starting the transfer, then expand the peer connection. In the stats, find the candidate-pair that is nominated with state succeeded and follow its local and remote candidate IDs. In Firefox, about:webrtc lists the ICE candidate pairs for each connection and marks the selected one. What to read:

  • candidateType of each side: host or srflx (or prflx) means direct; relay on either side means TURN.
  • relayProtocol on a relay candidate: udp, tcp or tls.
  • currentRoundTripTime on the pair. A round trip much longer than a ping between the two networks suggests a distant relay.

The same information is available from the page itself through the statistics API:

Browser console: which path is this RTCPeerConnection using?
const stats = await pc.getStats();
let pair;
stats.forEach((r) => {
  if (r.type === "transport" && r.selectedCandidatePairId) pair = stats.get(r.selectedCandidatePairId);
});
// Fallback for browsers without transport stats
if (!pair) stats.forEach((r) => {
  if (r.type === "candidate-pair" && r.nominated && r.state === "succeeded") pair = r;
});
const local = stats.get(pair.localCandidateId);
const remote = stats.get(pair.remoteCandidateId);
console.log(local.candidateType, local.relayProtocol ?? local.protocol,
            "->", remote.candidateType, "RTT s:", pair.currentRoundTripTime);

The field names come from the W3C WebRTC statistics specification. For the difference between the candidate types, see STUN vs TURN.

Endpoint limits: CPU, buffering, storage and background tabs

Encryption and CPU

AES-GCM is rarely the bottleneck on a modern laptop or desktop, because x86 processors with AES-NI and ARMv8 processors with the cryptography extensions run it in hardware. It can become the limit on low-end phones, older devices without those instructions, or when the JavaScript around it is inefficient: copying buffers several times, base64 encoding, or encrypting tiny pieces one at a time. The tell-tale sign is one CPU core near 100% on the slow device while the network sits partly idle. Moving the transfer to a stronger machine, or to a command-line client, fixes it.

Too little, or too much, data in flight

A reliable sender that waits for each acknowledgement before sending the next chunk is limited to one chunk per round trip. With 64 KB chunks and a 50 ms round trip, that is 64 KB / 0.05 s, about 1.3 MB/s, regardless of the bandwidth available. Throughput can only approach the link rate when the data in flight covers the bandwidth-delay product (bandwidth multiplied by round-trip time), so a sender needs a window of several chunks or a deep enough send buffer.

The opposite mistake is just as common. RTCDataChannel.send() never blocks; it queues data and raises bufferedAmount. A loop that ignores it can queue gigabytes in memory, after which the tab slows to a crawl, garbage collection stalls the event loop, or the channel closes. The correct pattern is a high-water mark: stop sending above it, and resume on bufferedamountlow. Very small messages add per-message overhead and event loop work, so tiny chunks also cost throughput. The WebRTC data channel guide covers message sizes and backpressure in detail.

Disk writes and browser storage

The receiver must write as fast as data arrives. A full or nearly full disk, a slow SD card or USB stick, or a browser that is buffering the file in memory because it has no streaming path will all show up as bursts of progress followed by pauses. In a browser, a fast receiver writes through a origin private file system (OPFS) handle in a worker, or streams the file into the browser's download manager through a service worker. Holding chunks in memory and building a Blob at the end is fine for small files and the wrong design for large ones.

Background tabs and power saving

Browsers throttle hidden pages. Chrome's documented rules check chained timers in a hidden tab at most once per second, and after five minutes hidden, once per minute (“intensive throttling”), unless the page has an active WebRTC connection or has played sound recently (Chrome timer throttling). A transfer whose sending loop is paced by timers can slow sharply when its tab is in the background, particularly when no WebRTC connection is open (for example on a WebSocket relay), because the WebRTC exemption then does not apply. Mobile operating systems go further and suspend background pages, and battery-saver modes reduce CPU and network activity. Keep the sending tab visible and the device plugged in for long transfers.

Step-by-step diagnosis

Work through the table from the top. Each row is a symptom you can observe, a check that confirms the cause, and the fix. Stop at the first row that matches: fixing a stage that is not the narrowest one will not help.

Diagnosing a slow peer-to-peer transfer, most common causes first.
SymptomCheckFix
Speed is steady and close to a fixed valueRun a speed test on the sender; compare its upload figure with the transfer rate.If they match, that is the cap. Send from a better-connected machine or wire the sender.
Fast on one network, slow on anotherSelected candidate pair in chrome://webrtc-internals or about:webrtc: is either side type relay?Allow UDP on the slow network, disconnect the VPN, or accept relay speed.
Relay candidate with relayProtocol tcp or tlsSame stats entry; also try another network.UDP is blocked. Allow outbound UDP to the TURN server, or expect lower speed on lossy links.
Two devices in the same room transfer slowlyBoth on Wi-Fi? Is the selected pair host-to-host or relay?Wire one side or move closer to the access point. If the pair is relay, see the connection-failure guide.
Slow only when the VPN is onCompare a transfer and a speed test with the VPN on and off.Split-tunnel the browser or pause the VPN during the transfer.
Speed collapses when the tab goes to the background or the screen locksKeep the tab in front and repeat.Keep both tabs visible, disable battery saver, keep the screen on for phones.
One CPU core near 100% on one device, network partly idleTask Manager, Activity Monitor or top during the transfer.Use a stronger device or a command-line client; close other heavy tabs.
Progress comes in bursts; receiver memory keeps climbingFree disk space and the browser's memory use on the receiver.Free space, use a browser that streams to disk, or receive with a CLI.
Own code: rate equals chunk size divided by round-trip timeCount chunks in flight; log bufferedAmount over time.Send a window of chunks; use a bufferedAmount high-water mark and bufferedamountlow.

How SRIFT behaves, and what speed to expect

SRIFT browser sessions use a relay-first design with a WebRTC head start. When a transfer starts, the browser checks whether a direct path is even possible: a short probe that succeeds if a STUN server returns a server-reflexive candidate (UDP works), or TURN credentials being available. If either holds, it opens a direct WebRTC data channel and gives it 3 seconds. If the channel is not open by then, or WebRTC is not possible on the network, the transfer runs over SRIFT's WebSocket relay. The browser console logs this decision on a line starting with Relay-first: that shows whether UDP and TURN were detected.

Throughput-relevant parameters in SRIFT 4.3.2 (from the source code and changelog).
PathParameters
Direct (WebRTC)Messages sized from the negotiated maximum (16 KB to 256 KB, 64 KB default); file read in 4 MB slices; sender pauses above 8 MB buffered and resumes at 2 MB; the receiver pauses the sender when more than 32 MB is waiting to be written.
Relay (WebSocket)256 KB chunks sent as base64, 16 chunks in flight with a sliding window of acknowledgements; each chunk sealed with AES-256-GCM.
Relay speedThe 4.3.2 changelog reports about 8-13 MB/s measured locally, up from about 2 MB/s with the older format. That is a local measurement, not a guarantee: across the internet both sides' links and their routes to the server set the ceiling.
Receiver memoryThe changelog records a 20 GB browser-to-browser transfer with about 100 MB of page memory, using streaming into Downloads.

Two consequences for speed. First, a transfer that starts on the relay, or falls back to it when the direct path fails mid-way, stays on the relay: SRIFT continues from the bytes already received and does not switch back, because changing transports mid-stream is where ordering bugs hide. Second, the relay window of 16 chunks of 256 KB keeps 4 MB in flight, which by the bandwidth-delay arithmetic above allows a theoretical 80 MB/s at a 50 ms round trip. On typical consumer connections the window is therefore not what limits the relay; the two access links and their routes to the server are.

Frequently asked questions

Is a TURN relay always slower than a direct connection?

Not always. TURN over UDP through a nearby, well-provisioned server adds a hop and a little round-trip time, and is often close to direct speed. TURN over TCP or TLS, a distant TURN server, or a busy one is where the drop becomes large.

Does end-to-end encryption slow down a file transfer?

On desktop and laptop CPUs with hardware AES instructions, AES-GCM is rarely the limit. On low-end phones, or in code that copies and base64-encodes every chunk, the CPU work around encryption can become the bottleneck.

Why is a transfer between two devices on the same Wi-Fi slow?

Each packet crosses the air twice, sender to access point and access point to receiver, and shares airtime with every other device on the channel. If the selected candidate pair is a relay rather than host-to-host, the traffic is also leaving the building and coming back.

References

  1. RFC 8831: WebRTC Data Channels (IETF)
  2. RFC 8656: Traversal Using Relays around NAT (TURN) (IETF)
  3. Identifiers for WebRTC's Statistics API (W3C)
  4. RTCDataChannel: bufferedAmount (MDN)
  5. Heavy throttling of chained JS timers beginning in Chrome 88 (Chrome for Developers)
  6. Origin private file system (MDN)

Try a direct transfer

Open a session in one browser, join it from another device, and send a file. No account, nothing to install on either side.