zellijterminaldotfiles

Zellij Session Manager — Every Terminal, Named, Resumable, Remote

Zellij Session Manager — Every Terminal, Named, Resumable, Remote

The Problem

Two laptops, two cities, terminal chaos. Sessions unnamed, untracked, and gone after reboot.

Cities

2

Bengaluru ↔ Guwahati

Sessions

??

Unnamed, untracked

After reboot

0

Everything gone

What was happening

1

Open terminal, cd ~/git/kri, start working

2

Open another terminal, cd ~/git/atlas, start working

3

Laptop restarts — both terminals gone, no idea what was running

4

SSH from Guwahati — can’t reach any sessions, start over

The core issue: Terminal sessions are anonymous, ephemeral, and local. No naming, no persistence, no remote access.

Why Zellij (not tmux)

Zellij gives us session resurrection without plugins, discoverable keybindings without memorization, and floating panes without configuration.

Zellij wins

Session restoreBuilt-in, no plugins
DiscoverabilityLive keybinding hints
Floating panesBuilt-in (Alt-p)
Layout filesKDL (human-readable)
Config to be productiveZero lines needed

tmux wins

Pre-installedEvery server
Plugin ecosystemMature (TPM)
ScriptingShell-scriptable
Community15+ years of guides
Memory~4MB vs ~22MB

The decision: Zellij for local dev (better UX, built-in resurrection). tmux stays on remote servers (pre-installed everywhere). The shell hook + Tailscale bridge the gap.

Architecture

4 scripts + 1 config + 1 shell hook = every terminal tracked, named, and resumable.

zj

capture

Existing terminal → Zellij session

sess

fzf

Pick from all sessions

shell hook

auto

New terminal → auto-create session

Complete architecture — animated LIVE

GUWAHATI Terminal + SSH ssh 198.51.100.27 sess → fzf picker Tailscale VPN cylon 198.51.100.27 cylon — Bengaluru Ubuntu 24.04 · 198.51.100.27 Shell Hook — auto-name from $PWD ~/.config/zellij/sess.sh Zellij Server — 0.44.3 SESSIONS ● kri 3 panes · attached ○ atlas 5 panes · detached ○ blog 2 panes · detached + any new terminal auto-creates here zj capture terminal into session sess fzf session picker Zellij Resurrection — auto-save to disk ~/.local/share/zellij/ disk persistence reboot LOCAL TERMINALS $ zj capture this shell → session named from $PWD $ cd ~/git/atlas next terminal auto-creates session "atlas" $ sess fzf picker → select session attach to any zj-migrate captures all of these

Session Lifecycle

From opening a terminal to resuming after reboot — every state transition.

State machine — animated transitions

Shell Opens Hook Checks Session exists New session Zellij Active zj capture zj-migrate Reboot Disk Saved Auto-Restore SSH Remote sess / zj -a Same Session NEW TERMINAL EXISTING TERMINALS REBOOT REMOTE ACCESS

Reboot Recovery

How sessions survive a laptop restart.

The chain

1

Zellij saves to disk — every pane layout, tab, scrollback → ~/.local/share/zellij/

2

Laptop reboots — all processes die, disk persists

3

Tailscale auto-connects — stable IP 198.51.100.27 returns

4

Open terminal — shell hook runs, finds saved session

5

Zellij restores — layout + scrollback back, empty shells in right positions

Important: Zellij restores the layout (panes, tabs, positions) and scrollback. It does not restore running processes. A python server.py that was running will need to be re-started. This is true for every multiplexer — no tool survives a reboot and brings back live processes.

config.kdl

Zellij configuration — mode-based keybindings, dark theme, sensible defaults.

Key design choices

SettingValueWhy
default_shellbashConsistent across terminals
scroll_buffer_size50000Deep scrollback for logs
pane_framesfalseClean look, less visual noise
mirror_sessiontrueNew panes open in current dir
themecustom darkMatches blog design

Keybinding summary

BindingActionMode
Alt-d / Alt-rNew pane (down / right)Pane
Alt-hjklMove between panesPane
Alt-fToggle fullscreen panePane
Alt-pFloating paneGlobal
Alt-tNew tabTab
Alt-1..5Switch to tab NTab
Alt-sDetachSession
Ctrl-pEnter pane mode (see all keys)Global
Ctrl-tEnter tab mode (see all keys)Global

zj — Capture Existing Terminal

The missing piece: bring an already-running terminal into Zellij.

Usage

# In any terminal — capture this shell into a Zellij session
$ zj
Creating session: kri

# Capture with explicit name
$ zj my-project
Creating session: my-project

# List all sessions
$ zj -l
kri   3 panes
atlas 5 panes

# Kill a session
$ zj -k old-project

# Attach from outside Zellij
$ zj -a kri

What “capture” means

zj creates a new Zellij session with the same $PWD. Your current shell continues until you type exit. On the next new terminal, the shell hook auto-attaches to this session.

Inside Zellij already? zj creates a new tab instead of a new session. This is how you expand your workspace without leaving Zellij.

sess — Fuzzy Session Picker

Type sess and fzf shows all Zellij sessions. Type to filter, enter to attach.

sess flow — animated

Terminal $ sess or: sess kri fzf — sess > kri atlas blog main Zellij — atlas Pane 1: $ kubectl get pods Pane 2: $ vim main.py Pane 3: $ docker logs -f Tab 1: deploy · Tab 2: debug Alt-p: floating pane

zj-migrate — Capture All Existing Terminals

One command to scan every running terminal and offer to create Zellij sessions for them.

How it works

$ zj-migrate
Scanning running terminals...

Found 3 terminal(s) to capture:

  DIRECTORY                              SESSION NAME
  ────────────────────────────────────── ────────────
  /home/dk/Documents/git/kri             kri          (new)
  /home/dk/Documents/git/atlas           atlas        (new)
  /home/dk/Documents/git/blog-drafts     blog-drafts  (new)

Create Zellij sessions for these? [y/N] y

  CREATE  kri /home/dk/Documents/git/kri
  CREATE  atlas /home/dk/Documents/git/atlas
  CREATE  blog-drafts /home/dk/Documents/git/blog-drafts

Done! 3 session(s) created.

List sessions:  zellij list-sessions
Attach:         zellij attach <session>
Fuzzy picker:   sess

What it scans: All bash/zsh/fish processes, all terminal emulator processes (gnome-terminal, kitty, alacritty, wezterm). Derives session names from their current working directories. Skips duplicates and Zellij-managed panes.

Shell Hook

The magic: every new terminal auto-creates/attaches to a Zellij session named after the directory.

The hook

# ~/.config/zellij/sess.sh — add to ~/.bashrc
if [[ -z "$ZELLIJ" ]]; then
  [[ $- == *i* ]] || return
  local session=$(basename "$PWD" | tr '.' '-')
  [[ "$session" == "$(basename "$HOME")" ]] && session="main"
  command -v zellij &>/dev/null || return
  if zellij list-sessions 2>/dev/null | grep -q "^${session}:"; then
    exec zellij attach "$session"
  else
    exec zellij --session "$session"
  fi
fi

How it works: On every shell startup, check if we’re inside Zellij. If not, derive a session name from $PWD. If the session exists → attach. If not → create. The exec replaces the shell process so Zellij takes over cleanly.

Edge cases handled

ScenarioBehavior
In $HOMESession named “main” (not “dk”)
Non-interactive shellSkipped (scripts, cron)
Zellij not installedSkipped gracefully
Already inside ZellijSkipped (no nested Zellij)
Directory with dotsDots → dashes (e.g. my.project → my-project)

Install

Everything you need, in order.

Quick install

# 1. Install Zellij (pre-built binary, fastest)
$ curl -sL https://github.com/zellij-org/zellij/releases/download/v0.44.3/zellij-x86_64-unknown-linux-musl.tar.gz | tar xz
$ mv zellij ~/.local/bin/

# 2. Create config
$ mkdir -p ~/.config/zellij
$ cat > ~/.config/zellij/config.kdl ooter

# 3. Add shell integration
$ echo 'source ~/.config/zellij/sess.sh' >> ~/.bashrc

# 4. Install scripts
$ cp zj zj-migrate sess ~/.local/bin/
$ chmod +x ~/.local/bin/{zj,zj-migrate,sess}

# 5. Restart shell
$ source ~/.bashrc

# 6. Capture existing terminals
$ zj-migrate

Prerequisites

ToolRequiredInstall
ZellijYesBinary above or cargo install zellij
fzfFor sessapt install fzf
TailscaleFor remoteAlready installed

Key Bindings

Shell

zjCapture terminal → session
zj nameCapture with name
zj -lList sessions
zj -k nameKill session
zj -a nameAttach to session
sessFuzzy session picker
zj-migrateCapture all terminals

Inside Zellij

Alt-d / Alt-rNew pane (down/right)
Alt-hjklMove between panes
Alt-fToggle fullscreen
Alt-pFloating pane
Alt-tNew tab
Alt-1..5Switch tab
Ctrl-pPane mode (all keys)
Ctrl-tTab mode (all keys)

Remote Access via Tailscale

From Guwahati, from any city, from any machine on your Tailscale network.

The flow

# From Guwahati (or anywhere on Tailscale)
$ ssh 198.51.100.27

# List all sessions
$ sess
# → fzf shows: ● kri  ○ atlas  ○ blog

# Or attach directly
$ zellij attach kri

# Everything is exactly where you left it
# Pane layouts, scrollback, tab names — all preserved

Tailscale advantage: Stable IP (198.51.100.27) that survives network changes. Auto-connects on boot. WireGuard encryption. No port forwarding, no DNS setup.

Cheat Sheet

Session Management
zj
zj name
zj -l
zj -k name
sess
zj-migrate
Inside Zellij (Alt-based, no prefix)
Alt-d / Alt-r
Alt-h / Alt-j / Alt-k / Alt-l
Alt-f
Alt-p
Alt-t
Alt-1..5
Alt-s
Alt-x
Alt-w
Ctrl-p
Ctrl-t
Ctrl-s

Enjoyed this post?

Get the next one in your inbox — only when I ship something worth reading.

Newsletter form not configured.

Or follow on Substack for the newsletter.

Comments via GitHub Discussions

Comments not configured. Set GISCUS env vars to enable.