All notes
5 min read

Licode in 2026: understanding the stack, and moving off it

What Nuve, ErizoController, ErizoAgent and ErizoJS each actually do, why Licode deployments become hard to build years later, and how its concepts map onto mediasoup and LiveKit if you decide to migrate.

AAAsghar AliFounder & Lead Engineer · Daniotech
Licode architecture: browser, ErizoController, Nuve, RabbitMQ, ErizoAgent, ErizoJS and the C++ Erizo media engine.
Licode architecture: browser, ErizoController, Nuve, RabbitMQ, ErizoAgent, ErizoJS and the C++ Erizo media engine.

Licode was one of the first credible open-source WebRTC platforms — an MIT-licensed stack out of Universidad Politécnica de Madrid that gave you a working SFU, a room API and a browser SDK at a time when the alternative was writing your own media server.

A lot of products were built on it. Many are still running. If you have inherited one, this is what you are looking at.

The architecture

Licode is not one process. It is five roles that talk over RabbitMQ, with MongoDB holding room state. Understanding which piece does what is most of the battle.

Component Language Responsibility
Nuve Node.js Management API. Creates rooms, issues client tokens, holds service credentials. Backed by MongoDB.
ErizoController Node.js Client-facing signalling over socket.io. Validates tokens, tracks who is in which room.
ErizoAgent Node.js Supervises a pool of ErizoJS worker processes on a host.
ErizoJS Node.js One media-handling process. Owns the publishers and subscribers assigned to it.
Erizo C++ The actual media engine — ICE, DTLS-SRTP, RTP/RTCP, forwarding. Exposed to Node through the ErizoAPI addon.

The flow for a joining client:

  1. Your backend calls Nuve to create a room and mint a token.
  2. The client hands that token to ErizoController over socket.io.
  3. ErizoController asks an ErizoAgent for capacity; the agent hands back an ErizoJS process.
  4. Media negotiates directly between the browser and Erizo, the C++ engine inside that process.

Two consequences fall out of this immediately. Signalling and media are separate processes, so a hung ErizoJS takes down the calls it owns without taking down signalling — which is why symptoms often present as "some rooms broke". And the RabbitMQ bus is a hard dependency: if it wobbles, controllers cannot reach agents and new joins fail while existing calls carry on happily.

Why old deployments are hard to rebuild

The stack itself is reasonable. The problem is almost always the build.

Erizo is C++ compiled against a specific generation of libraries — libnice for ICE, OpenSSL for DTLS, libsrtp, libav/ffmpeg. Those have all moved on, and several moved incompatibly. OpenSSL 1.1 to 3.x in particular broke a great many WebRTC media servers of that era.

So the common situation is: the code is fine, the running servers are fine, and nobody can produce a fresh build because the toolchain the build script expects no longer exists in any current base image.

If you are in that position, the pragmatic first step is not migration. It is pinning what you have:

# Freeze the build environment before touching anything else. An old base image
# is not elegant, but it converts "we cannot rebuild" into "we can rebuild".
FROM ubuntu:18.04

# Licode's own installer expects this vintage of toolchain.
RUN apt-get update && apt-get install -y \
      git build-essential python2.7 cmake pkg-config \
      libssl-dev libnice-dev libsrtp-dev libopus-dev libvpx-dev \
      rabbitmq-server mongodb-server curl \
 && rm -rf /var/lib/apt/lists/*

Get a reproducible image first. Then decide about the future with the pressure off. Migrating under an outage is how projects go badly.

Deciding whether to move

Licode still works. "Old" is not by itself a reason to rewrite a media layer, and a rewrite is a real project. Reasons that genuinely justify it:

  • You cannot build it, and pinning an ancient base image is not acceptable for your security posture.
  • You need features the engine does not have — modern simulcast handling, SVC, end-to-end encryption via insertable streams.
  • Nobody on the team can read the C++, so any media bug is unfixable in practice.
  • Scaling behaviour is opaque and you cannot reason about capacity per host.

Reasons that do not:

  • It is not the framework people are talking about this year.
  • A benchmark from a vendor whose product you would be buying.

Mapping the concepts

If you do migrate, the translation is mostly mechanical, because every SFU solves the same problems with different nouns.

Licode mediasoup LiveKit
Nuve room Router Room
Nuve token — (your own auth) Access token (JWT)
ErizoController Your signalling server Built in
ErizoJS process Worker Built in
Erizo.Stream publish Producer localParticipant.publish
Erizo.Stream subscribe Consumer Track subscription

The two targets differ in how much they hand you:

  • mediasoup is a library, not a server. You write the signalling, the room logic and the scaling. Maximum control, and closest in spirit to what Licode already made you do — which usually makes it the smaller conceptual jump.
  • LiveKit is a complete server with SDKs, auth, recording and scaling included. Much less code to write, at the cost of adopting its model.

If your Licode integration is thin — publish, subscribe, a room list — LiveKit will delete most of your code. If you have grown real logic into ErizoController, mediasoup will feel more familiar and you will keep the parts that work.

Migrating without a flag day

Do not cut over. Run both.

  1. Put a room-level switch behind a feature flag. New rooms go to the new stack; existing rooms stay on Licode until they end. Calls are short-lived, so the old system drains on its own within a day.
  2. Move signalling before media. Get your client talking to a new signalling layer that still drives Erizo underneath, if your abstraction allows it. That isolates the two risky changes.
  3. Instrument both. Log the selected ICE candidate pair type, join success rate and time-to-first-frame on each stack. You need numbers to justify finishing the migration.
  4. Keep TURN separate. Your TURN infrastructure is independent of the SFU. Migrating the media server is not an excuse to also replace the relay — change one thing at a time.

The room-scoped flag is what makes this safe. A media migration that has to happen at 2am for everyone at once is one that will be remembered.

Worth keeping

Licode got some things genuinely right, and they are worth carrying forward whatever you migrate to: separating the room API from signalling, isolating media into supervised worker processes, and treating tokens as the boundary between your application's auth and the media layer.

Those are the same shapes you will build in mediasoup, and the same ones LiveKit implements for you.


Related: P2P, SFU or MCU on choosing the topology, and setting up coturn for the relay layer that outlives whichever SFU you pick.

We migrate legacy and metered real-time stacks onto self-hosted LiveKit and mediasoup, and model the cost before and after.

  • #Licode
  • #Erizo
  • #WebRTC
  • #SFU
  • #Migration
  • #mediasoup

Working on this?

We build and fix real-time systems.

If your calls fail for a subset of users and you can't reproduce it, that's diagnosable. Take a live demo call and we'll walk the architecture with you.