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