Skip to content

Troubleshooting recordings#

Introduction#

This is a guide to help identify and solve common issues related to recordings in OpenVidu.

Recordings are handled by the Egress service in OpenVidu, built on top of LiveKit Egress . There are some general considerations to take into account:

  • Recordings are CPU intensive operations. For recordings to work smoothly, the nodes hosting the Egress service must have sufficient free cores.
  • The first symptom of something going wrong with recordings is usually missing recordings: you are expecting recording files to be available in your storage, but they are simply not there. Other common symptom is receiving an error 503 Service Unavailable when starting a new recording on-demand.
  • To be proactive when dealing with recording issues, it is highly recommended to enable webhooks in your OpenVidu deployment (learn how) and monitor it from your application's backend. In this way you can be notified of Egress-related events and errors as they happen.

On-demand vs Automatic recordings#

Recordings can be started on-demand or automatically:

  • On-demand recordings are started by calling any of the these operations of the LiveKit Egress API :
    • StartRoomCompositeEgress
    • StartTrackCompositeEgress
    • StartParticipantEgress
    • StartTrackEgress
    • StartWebEgress
  • Automatic recordings are started when a room is created with auto-egress enabled. To do so just include an egress field when calling the CreateRoom method: then the room will be automatically recorded during its lifetime. See auto-egress documentation for more details.

Warning

It is important to distinguish between on-demand recordings and automatic recordings for a simple reason: your application is able to catch and handle errors when starting on-demand recordings, while automatic recordings will fail silently. In that case, enabling webhooks (or inspecting the service logs) is the only way to be notified of these failures.

List of possible recording issues#

Below are some of the most common issues related to recordings in OpenVidu, along with their symptoms and possible solutions.

CPU exhausted#

Description

There are not enough free CPU cores to start a new egress.

Symptoms

  • The operation to start a new egress returns a 503 Service Unavailable error (text of the error: no response from servers). This applies when starting egress on-demand from your application.
  • Log lines of level WARN displaying can not accept request in service "egress". A typical log line of this type looks like this:

    2026-03-10T10:47:56.215Z    WARN    egress  stats/monitor.go:204 can not accept request
    {"nodeID": "NE_ynSpFdP6xfAS", "clusterID": "", "total": 16, "available": 3.175, "pending": 0,
    "used": 6.258, "activeRequests": 4, "activeWeb": 4, "memory": 2.9882, "memorySource": "proc_rss",
    "required": 4, "canAccept": false, "reason": "cpu", "error": "not enough CPU"}
    
  • Log lines displaying CPU exhausted in service "egress".

  • Log lines displaying pipeline frozen in service "egress".
  • Log lines displaying Can't record audio fast enough in service "egress".

Solutions

  • Scale out your OpenVidu deployment: deploy it in nodes with more CPU cores or add more Media Nodes to your cluster.
  • Consider using a less CPU intensive egress type: Track Egress does not require transcoding and composition, so it is much less CPU intensive than other egress types such as Room Composite Egress .
  • Review your egress.yaml configuration file, specifically the properties within cpu_cost:

    cpu_cost:
        max_cpu_utilization: 0.8
        room_composite_cpu_cost: 2.0
        audio_room_composite_cpu_cost: 1.0
        web_cpu_cost: 2.0
        audio_web_cpu_cost: 0.5
        participant_cpu_cost: 1.0
        track_composite_cpu_cost: 1.0
        track_cpu_cost: 0.5
    

    These properties determine two different things. OpenVidu configures sane defaults in all of them to prevent CPU exhaustion in most scenarios, but you may want to review and adjust them according to your specific use case:

    • The max_cpu_utilization property is the CPU usage threshold of the node above which the egress service won't accept new requests.
    • All the [...]_cpu_cost properties determine the amount of free CPU cores required in the node to accept a new egress request of each type.

We strongly recommend reading the Load balancing of egress documentation.

Wrong auto-egress configuration#

Description

By default, OpenVidu will automatically create a new room when a user tries to connect to a room that does not exist yet. This behavior can be disabled in livekit.yaml configuration file:

room:
  auto_create: false

When a room is auto-created on participant join, OpenVidu uses the room settings included in that participant’s token (RoomConfiguration ).

A common problem appears when these chain of events happens:

  1. Your application's backend explicitly creates a room with a specific RoomConfiguration. For example, with an egress field to enable auto-egress.
  2. The room is unexpectedly deleted because of a timeout (properties departure_timeout or empty_timeout in livekit.yaml).
  3. A participant tries to join the room using a token that does not include the same egress field in its RoomConfiguration.
  4. OpenVidu auto-creates the room again, but now using the token’s RoomConfiguration, not the original one.

In this case, the room will not have auto-egress enabled, and therefore it will not be recorded. This behavior is also silent, as it is not an actual error but just a logic issue in your application.

Symptoms

You encounter this set of logs related to a room (in this example, room DailyMeeting):

  1. You explicitly create the room from your application's backend with autoegress enabled:

    2026-01-19T09:00:03.302Z   INFO   openvidu.api   service/twirp.go:128   API RoomService.CreateRoom
    {"service": "RoomService", "method": "CreateRoom", "room": "DailyMeeting", "request":
    {"name": "DailyMeeting", "metadata": "<redacted (0 bytes)>", "egress": {"tracks": {"filepath":
    "recordings/{room_name}/{track_source}-{time}"}}}, "duration": "8.808101ms", "status": "200"}
    
  2. The room is automatically deleted due to a timeout:

    2026-01-19T09:04:43.197Z    INFO    openvidu.room   rtc/room.go:854 closing idle room
    {"room": "DailyMeeting", "roomID": "RM_5dHqrCZBr5X5", "reason": "empty timeout"}
    
  3. A participant connects to the room with a token that does not have autoegress enabled (no egress field in its RoomConfig):

    2026-01-19T09:00:02.925Z    INFO    openvidu    service/roommanager.go:432  starting RTC session
    {"room": "DailyMeeting", "roomID": "RM_u8NSwnuczia6", "participant": "Alice", "pID": "PA_CMwzkoL6Vp3q",
    "remote": false, "nodeID": "ND_QLa7LB7937db", "numParticipants": 1, "participantInit":
    {"Identity": "1589577", "Reconnect": false, "ReconnectReason": "RR_UNKNOWN", "AutoSubscribe": false,
    "Client": {...}, "Grants": {...}, "SIP": {...}, "Agent": {...},
    "RoomConfig": {"metadata": "<redacted (0 bytes)>"}, "RoomPreset": ""},
    "Region": "", "AdaptiveStream": true, "ID": "", "SubscriberAllowPause": "not-set", "DisableICELite": false, "CreateRoom": {"name": "DailyMeeting", "metadata": "<redacted (0 bytes)>"}}
    

Solutions

  • Consider using on-demand egress requests instead of auto egress to record rooms.
  • Consider disabling auto-creation of rooms in file livekit.yaml. This enforces rooms to always be created explicitly from your backend before a participant can join to it with a token. Of course, it also requires handling connection errors in the frontend if a participant tries to join a non-existing room.

    room:
      auto_create: false
    
  • If you want both auto-egress and auto-creation of rooms, make sure to include the same egress field in the RoomConfiguration in both the CreateRoom request and the participant's access token . In this way rooms will always behave the same way, no matter if they are explicitly created from your backend or auto-created when a participant tries to join.

  • Increase the room timeout properties in livekit.yaml. This can reduce the probability of rooms being cleaned up before a participant tries to join:

    room:
        # number of seconds to keep the room open if no one joins
        empty_timeout: 300
        # number of seconds to keep the room open after everyone leaves
        departure_timeout: 20
    

Misconfiguration of external storage#

Description

You have configured an external storage for your recordings, but the recording files are not being uploaded to it. This can be caused by a misconfiguration or a connectivity issue between OpenVidu nodes and the external storage.

External storage is configured in the egress.yaml configuration file globally (with storage property):

storage:
  s3:
    ...
  azure:
    ...
  gcp:
    ...
  alioss:
    ...

You can also configure external storage programmatically on a per-request basis when starting an egress.

Symptoms

  • You don't see recording files in your external storage, and the backup folder for egress in your OpenVidu node (/opt/openvidu/data/egress_data/home/egress/backup_storage) is accumulating files:

    root@media-node-1:/opt/openvidu/data/egress_data/home/egress/backup_storage# ls -la
    total 19036
    drwxr-xr-x 2 admin admin     4096 Mar 10 14:16 .
    drwxr-xr-x 8 admin admin     4096 Mar 10 10:47 ..
    -rw-r--r-- 1 admin root       335 Mar 10 14:11 EG_53MKZu5havzx.json
    -rw-r--r-- 1 admin root       335 Mar 10 14:09 EG_ZgeLTs8FdtzU.json
    -rw-r--r-- 1 admin root       448 Mar 10 14:16 EG_fnvyJkL8YVyt.json
    -rw-r--r-- 1 admin root  14721461 Mar 10 14:09 RoomComposite-RM_Mi5Lau9HWGQ8-TestRoom-2026-03-10T140854.mp4
    -rw-r--r-- 1 admin root   1772593 Mar 10 14:11 RoomComposite-RM_Mi5Lau9HWGQ8-TestRoom2-2026-03-10T141055.mp4
    -rw-r--r-- 1 admin root   2969013 Mar 10 14:16 TrackComposite-RM_JpcnziWBTEdY-TestRoom3-2026-03-10T141625.mp4
    
  • The EgressInfo objects returned by the Egress API or included in the egress_ended webhook event contains:

    • Field backup_storage_used with value true.
    • Or any other field with the substring backup_storage in its value (E.g. manifestLocation = '/home/egress/backup_storage/EG_f5nHLam4xLb8.json').

Solutions

  • Review the configuration of the external storage: in the egress.yaml if you are using the global declarative configuration, or in the body of the CreateRoom or StartEgress operations if you are using the programmatic per-request configuration.
  • Review the connectivity between your OpenVidu nodes and the external storage. Check if there are any network issues or firewall rules preventing OpenVidu nodes from reaching the external storage.

No disk space free#

Description

There is not enough free disk space in the node hosting the egress to start a new recording.

Symptoms

  • The operation to start a new egress returns a 503 Service Unavailable error (text of the error: no response from servers). This applies when starting egress on-demand from your application.
  • The egress service logs a warning message can not accept request followed by a JSON object containing "error": "not enough disk space":

    {"nodeID": "NE_xRcu2tzJD5mC", "clusterID": "", "total": 16, "available": 12.022278481012659, "pending": 0, "used": 0, "activeRequests": 0, "activeWeb": 0, "memory": 0.06648635864257812, "memorySource": "proc_rss", "availableDiskMB": 30704.07421875, "minDiskSpaceMB": 99999999, "canAccept": false, "reason": "disk", "error": "not enough disk space"}
    
  • Webhook event egress_ended with status EGRESS_FAILED and error mkdir /home/egress/tmp/EGH_XXXXXXXXXXXX: no space left on device

    {"event": "egress_ended", "id": "EV_zxMEf5XPp5yV", "webhookTime": 1771329474, "url": "http://127.0.0.1:6080/livekit/webhook", "egressID": "", "status": "EGRESS_FAILED", "error": "mkdir /home/egress/tmp/EGH_XXXXXXXXXXXX: no space left on device", "queueDuration": "7.815288ms", "qLen": 0, "sendDuration": "8.05349ms"}
    

Solutions

Increase disk space in the node hosting the egress.

Egress CPU overload killer#

Description

By default, OpenVidu will kill active egresses under sustained high CPU load (see Egress CPU overload killer). This is generally expected and desired, but it can impact active egresses, causing them to be interrupted.

Symptoms

  • The recordings are incomplete. They appear to be cut off unexpectedly.
  • Log lines of level WARN displaying killing egress due to sustained high cpu in service "egress".

Solutions

  • You can disable the Egress CPU overload killer by setting property openvidu.disable_cpu_overload_killer to true in the egress.yaml configuration file. This must be done with caution and care, as it could lead to overloading the entire Media Node and affecting the performance of other processes.
  • In the end this is just an under-provisioning issue: consider scaling out your OpenVidu deployment. Deploy it in nodes with more CPU cores or add more Media Nodes to your cluster.

Track disappears before the recorder attaches (track not found)#

Description

When recording individual tracks with Track Egress (on-demand via StartTrackEgress, or with per-track auto-egress), OpenVidu launches one egress per published track, shortly after the track is published. The recorder then needs a moment to connect to the room and subscribe to the track: usually well under a second, occasionally a little longer. If the track is unpublished during that window, the recorder never finds it, and the egress fails after waiting up to 30 seconds.

The most common trigger is participant reconnection: when a client reconnects, its camera and microphone are republished with new track IDs, so the egress that was launched for the old track ID is orphaned. Other triggers are very short-lived publishes and quick unpublishes. This is a startup race tied to the track lifecycle, not a capacity problem.

In almost all cases no established recording is lost: the participant's stream keeps being recorded under the new track ID, with at most a small gap at the instant of the reconnection.

Symptoms

  • Log lines displaying egress_failed in service "egress", with an error of the form track TR_... not found, a code of 404 and details End reason: Failure:

    2026-05-14T10:00:30.600Z    INFO    egress  info/io.go:273  egress_failed
    {"nodeID": "NE_p9q8R7s6T5u4", "clusterID": "", "egressID": "EG_x7y8Z9w0V1u2",
    "requestType": "track", "outputType": "file", "error": "track TR_a1b2C3d4E5f6g7 not found",
    "code": 404, "details": "End reason: Failure"}
    
  • Webhook event egress_ended with status EGRESS_FAILED and the same track ... not found error:

    {"event": "egress_ended", "egressID": "EG_x7y8Z9w0V1u2", "status": "EGRESS_FAILED",
    "error": "track TR_a1b2C3d4E5f6g7 not found", ...}
    
  • The failure occurs around 30 seconds after the egress was requested (the recorder's wait timeout).

  • The pattern is associated with participants that reconnect frequently.

To confirm it is a track-lifecycle race, reconstruct the track's lifecycle across the "openvidu" and "egress" services. A textbook sequence (the track is published and unpublished less than half a second later, before the recorder could attach):

10:00:00.100  openvidu  mediaTrack published   trackID TR_a1b2C3d4E5f6g7 (audio, MICROPHONE)
10:00:00.540  openvidu  track_unpublished      TR_a1b2C3d4E5f6g7  (the track lived ~0.44 s)
10:00:00.600  egress    request validated      egressID EG_x7y8Z9w0V1u2, track_id TR_a1b2C3d4E5f6g7
10:00:30.600  egress    egress_failed          "track TR_a1b2C3d4E5f6g7 not found"  (+30 s)

Then look for a new mediaTrack published from the same participant identity in the same room shortly afterwards: that is the reconnected track, which continues to be recorded under its new ID.

Solutions

  • In most deployments this is expected and benign; no action is needed beyond confirming that the participant's stream is captured under another track ID.
  • It is not caused by egress capacity limits, a specific node, or storage.

The recorder attaches but no media arrives (Source closed)#

Description

The recorder connects and subscribes to the track successfully, but the recording pipeline never starts because no media ever arrives from the publisher. When the track finally closes, the egress aborts having recorded nothing.

This is almost always a camera track that is published without sending video: a participant who joins with the camera off or muted (a muted track stays published but sends no media), or whose camera has not started producing frames yet. It can also be a reconnection orphan: the track belongs to a peer connection that is being replaced, so media never stabilizes before the new connection takes over.

It is not a network, egress-node or capacity problem. The same peer connection's other tracks (for example the microphone) typically record fine at the same time, which proves the media path works; there is simply no video being sent.

Symptoms

  • Log lines displaying egress_aborted in service "egress", with error Stop called before pipeline could start, a code of 412 and details End reason: Source closed:

    2026-05-14T10:12:45.320Z    INFO    egress  info/io.go:273  egress_aborted
    {"nodeID": "NE_m2n3B4v5C6x7", "clusterID": "", "egressID": "EG_k3j4H5g6F7d8",
    "requestType": "track", "outputType": "file", "error": "Stop called before pipeline could start",
    "code": 412, "details": "End reason: Source closed"}
    
  • Webhook event egress_ended with status EGRESS_ABORTED:

    {"event": "egress_ended", "egressID": "EG_k3j4H5g6F7d8", "status": "EGRESS_ABORTED",
    "error": "Stop called before pipeline could start", ...}
    
  • In the egress logs the recording reaches pipeline paused (and the "openvidu" service logs starting with dummy forwarding for the egress subscriber), but it never reaches starting track synchronizer, pipeline playing or egress_active, the markers that the first media packet arrived. A healthy recording reaches all of them within a fraction of a second of subscribing.

  • It predominantly affects video/camera tracks.

To confirm the camera simply was not sending video, check whether the same participant's audio track recorded successfully during the same session. It usually did, on the same connection.

Solutions

  • Benign in most cases: a camera published while off has nothing to record, and reconnection orphans are recorded under the replacement track.
  • If recordings you expect are genuinely missing, verify on the client side that video tracks are only published when the camera is actually active (or handle mute / camera-off state appropriately), and reduce client reconnections.
  • As with track not found, it is not an egress capacity or node issue.