niri-wm/niri · error · anyhow::Error

no free X11 display found after 50 attempts

Error message

no free X11 display found after 50 attempts

What it means

When spawning Xwayland, niri picks a free X display number by trying to exclusively create the lock files /tmp/.X{n}-lock for n in start..start+50 (O_EXCL create). If all 50 attempts hit an existing lock file, it gives up with this error and Xwayland cannot start. Existing locks are usually stale — left by crashed X servers/Xwayland processes (the FIXME notes dead-process locks are not reused yet).

Source

Thrown at src/utils/xwayland/mod.rs:77

        (x11_tmp.st_mode & 0o1000) == 0o1000,
        "X11 directory is missing the sticky bit"
    );

    Ok(())
}

fn pick_x11_display(start: u32) -> anyhow::Result<(u32, OwnedFd, Unlink)> {
    for n in start..start + 50 {
        let lock_path = format!("/tmp/.X{n}-lock");
        let flags = OFlags::WRONLY | OFlags::CLOEXEC | OFlags::CREATE | OFlags::EXCL;
        let Ok(lock_fd) = rustix::fs::open(&lock_path, flags, 0o444.into()) else {
            // FIXME: check if the target process is dead and reuse the lock.
            continue;
        };
        return Ok((n, lock_fd, Unlink(lock_path)));
    }

    Err(anyhow!("no free X11 display found after 50 attempts"))
}

fn bind_to_socket(addr: &SocketAddr) -> anyhow::Result<UnixListener> {
    let listener = UnixListener::bind_addr(addr).context("error binding socket")?;
    Ok(listener)
}

#[cfg(target_os = "linux")]
fn bind_to_abstract_socket(display: u32) -> anyhow::Result<UnixListener> {
    use std::os::linux::net::SocketAddrExt;

    let name = format!("/tmp/.X11-unix/X{display}");
    let addr = SocketAddr::from_abstract_name(name).unwrap();
    bind_to_socket(&addr)
}

fn bind_to_unix_socket(display: u32) -> anyhow::Result<(UnixListener, Unlink)> {
    let name = format!("/tmp/.X11-unix/X{display}");

View on GitHub (pinned to 606284464d)

Solutions

  1. Verify no live X servers hold them: 'pgrep -a "Xwayland|Xorg|Xvfb"', then remove stale locks: 'rm /tmp/.X[0-9]*-lock' and stale sockets 'rm /tmp/.X11-unix/X*' (only for display numbers not in use).
  2. Kill and let niri respawn Xwayland cleanly (log out and back in) so it picks the freed display number.
  3. Free display numbers by shutting down unneeded nested X servers (Xvfb from CI, x11vnc) that squat low display numbers.
  4. As a last resort reboot (or remount tmpfs /tmp) to clear the lock directory entirely.

Example fix

# before: 50 lock files -> "no free X11 display found after 50 attempts"
ls /tmp/.X*-lock
# after: clean stale locks for dead servers and restart the session
pgrep -a Xwayland   # confirm which are alive
rm -f /tmp/.X*-lock /tmp/.X11-unix/X*
niri msg action do-screen-transition  # or just relaunch the app / relogin
Defensive patterns

Strategy: fallback

Validate before calling

# shell: detect stale locks (dead PID) before launching Xwayland consumers
for f in /tmp/.X*-lock; do
  [ -e "$f" ] || continue
  pid=$(cat "$f")
  if ! kill -0 "$pid" 2>/dev/null; then echo "stale lock: $f (pid $pid dead)"; fi
done

Try / catch

match pick_x11_display(start) {
    Ok(x) => x,
    Err(_) => {
        // fallback: sweep dead-owner lock files once, then retry the range
        clean_stale_x11_locks();
        pick_x11_display(start).context("no free X11 display even after cleaning stale locks")?
    }
}

Prevention

When it happens

Trigger: 50 consecutive /tmp/.X*-lock files present: many crashed Xwayland/X sessions accumulating locks in a long-lived /tmp, another X server (or several) legitimately holding lock files in that range, or /tmp not being cleaned because the system has not rebooted and processes leaked locks.

Common situations: Long-running machines where compositor crashes or force-killed Xwayland leave /tmp/.X1-lock .. .X50-lock behind; nested X servers (Xvfb, x11vnc, distroboxes) consuming display numbers; /tmp on tmpfs cleared only at reboot on a system that never reboots.

Related errors


AI-assisted analysis of niri-wm/niri@606284464d (2026-08-16). Data as JSON: /api/errors/1018e71d8d0ffdd8. Report an issue: GitHub.