Skip to main content

Jitsi: Diagnosing Kicks & WS Drops

This runbook captures the diagnostic pattern from the May 2026 incident where users on book.aiqlick.com were being silently kicked from meetings every 60–90 seconds. The fixes are already deployed; this page exists so future you can recognise the same fingerprint quickly if it regresses, or so you can rule it out before going looking elsewhere.

TL;DR

SymptomRoot causeWhere it lives
Member role changed from PARTICIPANT to VISITOR - not supported! in Jicofo logs, repeating every ~60sNginx default proxy_read_timeout 60s killed the idle XMPP WebSocket; smacks resume failed; Prosody evicted the occupant via the kick code pathjitsi-deploy/nginx/custom-meet.conf (server-scope proxy_read_timeout 3600s)
Mobile users dropping after brief network blips (>60s)Upstream Prosody smacks_hibernation_time = 60s too short for handovers / sleepjitsi-deploy/custom-prosody.cfg.lua (smacks_hibernation_time = 600)
Users with screen share dropping ~10–30s after sharing startsSame as #1 — share triggers a session re-invite (transport=null Colibri update) on top of an already-stale WSCovered by the nginx fix

If you see the VISITOR-role log line again, assume the nginx custom config got blown away or the override isn't being appended before chasing anything else.

The Fingerprint

A real kick cascade has three correlated signals — see all three before pulling the trigger on changes.

1. Jicofo log: VISITOR role transition

Jicofo ... ChatRoomMemberImpl.processPresence#198: Member role changed from
PARTICIPANT to VISITOR - not supported!
Jicofo ... JitsiMeetConferenceImpl.terminateParticipant#1036:
Terminating <id>, reason: gone, send session-terminate: false

The meeting_id= field on the WARNING line is empty (it's populated for live members and blank in the leave presence). Reason is always gone, never expired or kick.

2. Session lifetime clusters at ~60–65 seconds

ssh jitsi "docker logs jitsi-jicofo-1 --since 30m 2>&1 \
| grep -oE 'LIFE_TM_SEC=[0-9]+' | sort -u"

A healthy server shows widely varied LIFE_TM_SEC values (sessions live as long as the meeting). The kick cascade fingerprint is values tightly clustered between 60–65 — that's the nginx 60s timeout firing on every WS. Any other distribution means the cause is elsewhere.

3. Cascade: OWNER demoted → all participants demoted within ~500ms

When the room's OWNER's WebSocket dies, the room loses its moderator and Prosody cascades the eviction to every other occupant within the same Jicofo log second:

12:48:49.951  PARTICIPANT → VISITOR  (id=0ab4b0f3)
12:48:50.149 PARTICIPANT → VISITOR (id=8f0c8625)
12:48:50.205 PARTICIPANT → VISITOR (id=d71fb32c)
12:48:50.452 OWNER → VISITOR (id=57c0bde1) ← the trigger

Independent network failures don't synchronise like that. If you see four+ kicks within a second on the same room=, it's the cascade.

How to Verify the Fixes Are Live

# Nginx WS timeout (must be 3600s)
ssh jitsi "docker exec jitsi-web-1 nginx -T -c /config/nginx/nginx.conf 2>/dev/null \
| grep -E 'proxy_read_timeout|proxy_send_timeout'"
# Expected:
# proxy_read_timeout 3600s;
# proxy_send_timeout 3600s;

# Prosody smacks override (must be 600 / 10 / 5)
ssh jitsi "docker exec jitsi-prosody-1 cat /config/conf.d/custom-prosody.cfg.lua \
| grep smacks"
# Expected:
# smacks_hibernation_time = 600;
# smacks_max_unacked_stanzas = 10;
# smacks_max_old_sessions = 5;

If either is missing, run the deploy job manually: gh workflow run deploy.yml -R AiQlickProject/jitsi-deploy.

The Mechanism (for the curious)

The kick presence in step 7 has role=visitor because Prosody's set_role(jid, nil) (the internal kick implementation in mod_muc) emits the lowest-rank role on the way out. Jicofo's Java code doesn't recognise mid-conference visitor demotion as legitimate (it expects only join-time visitor assignment, e.g. from mod_fmuc overflow), so it logs not supported! and terminates the participant.

What's NOT This Pattern

Easy false positives — these are different bugs and the fixes above won't help:

  • Single isolated VISITOR kick on jigasibrewery@internal-muc.meet.jitsi — that's a Jigasi internal MUC kick, not user-facing. Investigate Jigasi's brewery registration retries instead.
  • reason: expired (not reason: gone) — the JWT itself expired (default exp = 24h). Backend's JitsiMeetService.generateJwtToken controls this; bump the expiresIn option if needed for long-running rooms.
  • Single user dropped, others stay connected — that user's local network. Check their browser console for ICE failed / TURN errors and have them retry through coturn (UDP/3478, TCP/443).
  • Gradual quality degradation, no kick — capacity issue. Check docker stats jitsi-jvb-1 and JVB BandwidthAllocator log lines; consider lowering channelLastN or splitting JVB to its own EC2.
  • Jitsi Services overview — full service architecture, where the custom configs slot in.
  • Jitsi Deploymentdeploy.sh mechanics, including the WEB_RECREATE flag that ships custom-config / nginx-conf changes.