Using ExpressTURN with PeerJS

Five-minute integration. Drop your credentials in, ICE handles the rest.

What you'll have at the end

A working PeerJS connection that falls back to TURN when your peers can't reach each other directly — so the call still connects on hotel Wi-Fi, mobile carriers, and corporate firewalls.

1. Get credentials

Sign up at expressturn.com/signup, then copy your turn_username and turn_password from the dashboard. Free tier gets you 1 TB/month.

2. Pass them to PeerJS

// npm install peerjs
import { Peer } from 'peerjs';

const peer = new Peer('your-peer-id', {
  config: {
    iceServers: [
      { urls: 'stun:stun.expressturn.com:3478' },
      {
        urls: [
          'turn:relay1.expressturn.com:3478?transport=udp',
          'turn:relay1.expressturn.com:3478?transport=tcp',
          'turns:relay1.expressturn.com:443?transport=tcp'
        ],
        username: 'YOUR_EXPRESSTURN_USERNAME',
        credential: 'YOUR_EXPRESSTURN_PASSWORD'
      }
    ],
    iceTransportPolicy: 'all'
  }
});

peer.on('open', id => console.log('peer ready:', id));
peer.on('connection', conn => {
  conn.on('data', d => console.log('received:', d));
});

3. Verify TURN actually works

The trap with TURN is silent fallback — your dev machine connects directly via local network and you never test the relay path. Force every connection through TURN for testing:

// In dev, set iceTransportPolicy to 'relay' to force TURN
const peer = new Peer({
  config: {
    iceServers: [/* same as above */],
    iceTransportPolicy: 'relay'  // every packet goes through TURN
  }
});

Open chrome://webrtc-internals while testing. You should see the active candidate pair use relay as its candidate type.

Common pitfalls

  • Forgetting iceTransportPolicy: 'all': Default is fine; explicitly setting it makes intent obvious.
  • Hard-coding credentials in client bundle: Free-tier static credentials are fine for hobby use. For production at scale, upgrade to shared-secret auth and mint per-session creds server-side.
  • Skipping TLS-443: Always include turns:relay1.expressturn.com:443 — it's the only path that survives strict corporate firewalls.
  • Wrong port for TURNS: turns://...:443, not turn://...:443. The s matters.

Premium: per-user credentials

If you're running PeerJS at scale and don't want a leaked client bundle to expose your TURN password, mint short-lived credentials server-side using the shared-secret auth example. Your server hands each PeerJS client a fresh username/password pair valid for an hour.

Related: simple-peer recipe · TURN for social apps · TURN for multiplayer games

Done. Ship it.

Get Free TURN Credentials