# GNU Screen: sessions that survive an SSH drop

LLMS index: [llms.txt](/en/llms.txt)

---

A long `apt upgrade`, a migration, a build — then the laptop sleeps. SSH dies, the process gets SIGHUP and dies with it. **GNU Screen** keeps the terminal on the server: disconnect, come back, the work is still there.

It is not a `nohup` replacement and not “another SSH”. It is a multiplexer: named sessions, several windows inside one, a shared session for two people. On older RHEL/Debian boxes `screen` is often already installed when `tmux` is not.

## The minimal loop

On the server:

```bash
screen -S deploy
```

That is a normal shell. To leave without killing processes: `Ctrl-a`, release, then `d`. The session stays **Detached**.

List and resume:

```bash
screen -ls
screen -r deploy
```

If it is still **Attached** on another terminal (left it open at the office):

```bash
screen -d -r deploy
```

Detach there first, attach here.

> [!NOTE]
> The prefix is `Ctrl-a`. Do **not** hold both keys together: `Ctrl-a`, release, then the letter. `Ctrl-a a` sends a real Ctrl-a into the program inside (needed by `emacs`, rarely by `bash`).

## Install

```bash
# Debian / Ubuntu
sudo apt install screen

# RHEL / Alma / Rocky
sudo dnf install screen

# Alpine
sudo apk add screen
```

Check with `screen -v`. Vertical splits (`Ctrl-a |`) exist since GNU Screen 4.1; very old boxes will not have them.

## Command-line flags

| Flag | What it does | Example |
| --- | --- | --- |
| `-S name` | create or address a session by name | `screen -S logs` |
| `-ls` / `-list` | list sessions | `screen -ls` |
| `-r [name]` | attach a detached session | `screen -r logs` |
| `-d -r name` | detach elsewhere, attach here | `screen -d -r logs` |
| `-R` | attach if it exists, otherwise create | `screen -R logs` |
| `-x [name]` | attach **without** detaching the other side | `screen -x logs` |
| `-dmS name command` | start in the background, already Detached | `screen -dmS backup /opt/backup.sh` |
| `-L` | write `screenlog.N` in the current directory | `screen -L -S migrate` |
| `-Logfile file` | log path (with `-L`) | `screen -L -Logfile /tmp/migrate.log -S migrate` |
| `-X command` | send a command into a live session | `screen -S logs -X stuff 'tail -f /var/log/nginx/error.log\n'` |
| `-wipe` | drop dead sockets from the list | `screen -wipe` |

The `-S` name is what you look for in `screen -ls`. Without a name you get something like `12345.pts-0.hostname` — awkward to recall.

Typical background job that must survive an SSH drop:

```bash
screen -dmS pg-dump pg_dump -Fc -f /backup/app.dump app
screen -ls
# later
screen -r pg-dump
```

When the command exits, the session usually disappears. Keep a shell afterwards:

```bash
screen -dmS build bash -lc 'make -j"$(nproc)"; exec bash'
```

## Windows inside a session

One session, several windows: build, logs, a second shell. Switch without a new SSH connection.

| Key | Action |
| --- | --- |
| `Ctrl-a c` | new window |
| `Ctrl-a n` / `Ctrl-a p` | next / previous |
| `Ctrl-a 0` … `Ctrl-a 9` | jump by number |
| `Ctrl-a "` | window list, pick with arrows |
| `Ctrl-a '` | jump by number or name |
| `Ctrl-a A` | rename the current window |
| `Ctrl-a Ctrl-a` | last active window |
| `Ctrl-a k` | close the window (asks to confirm) |
| `Ctrl-a \` | kill every window and quit screen |

Window titles help once you have more than two: `Ctrl-a A` → `nginx-log`.

## Session, copy, split

| Key | Action |
| --- | --- |
| `Ctrl-a d` | detach; processes keep running |
| `Ctrl-a D D` | power detach (other displays too) |
| `Ctrl-a ?` | key help |
| `Ctrl-a :` | screen command line (`quit`, `sessionname`, …) |
| `Ctrl-a a` | send Ctrl-a into the window |
| `Ctrl-a [` | copy mode / scrollback |
| `Ctrl-a ]` | paste screen’s paste buffer |
| `Ctrl-a Esc` | same as `Ctrl-a [` |
| `Ctrl-a S` | split horizontally (region above/below) |
| `Ctrl-a \|` | split vertically |
| `Ctrl-a Tab` | focus the other region |
| `Ctrl-a X` | close this region (window stays) |
| `Ctrl-a Q` | keep only this region |
| `Ctrl-a H` | toggle `screenlog.N` |
| `Ctrl-a M` | monitor the window for activity (bell) |
| `Ctrl-a x` | lock the session (user password) |

Scrollback: `Ctrl-a [`, then arrows or `PageUp` / `PageDown`. Select: `Space` to start, arrows, `Enter` to copy into screen’s buffer. Leave the mode with `Esc`. Paste with `Ctrl-a ]`. This is **not** the system clipboard: the buffer lives inside screen.

After a split the new region is empty until you focus it (`Ctrl-a Tab`) and pick a window (`Ctrl-a n` or `Ctrl-a "`).

## Server-side workflows

**Long deploy.** Named session, log on disk, close the lid:

```bash
screen -L -Logfile ~/migrate.log -S migrate
# inside: ansible-playbook -i prod site.yml
# Ctrl-a d
```

In the morning: `screen -r migrate`, or just `tail -f ~/migrate.log`.

**Several jobs in one SSH.** Session `ops`, windows `build`, `journal`, `sql`:

```bash
screen -S ops
# Ctrl-a c  — another window
# Ctrl-a A  — name it
```

**Paired view.** Same user, both attached:

```bash
# first
screen -S incident
# second, without kicking the first off
screen -x incident
```

Both see the same terminal. Faster than “paste me the output” during an incident.

**Push a command into a session already running** — no interactive attach:

```bash
screen -S ops -X screen bash
screen -S ops -X stuff 'systemctl status nginx\n'
```

`-X screen` opens a window; `stuff` types into it. `\n` is Enter.

## A short `.screenrc`

Default scrollback is tiny, the startup banner gets in the way, and window names are easy to miss. In `~/.screenrc`:

```screen
startup_message off
vbell off
defscrollback 20000
shell -$SHELL

hardstatus alwayslastline
hardstatus string "%{= kw}%-w%{= BW}%n %t%{= kw}%+w %= %H %l %Y-%m-%d %c"
```

`defscrollback` is how many lines `Ctrl-a [` can walk. `hardstatus` is the bar at the bottom: windows, host, load, time. The file is read when a session is **created**; live sessions pick it up only after you recreate them.

You do not have to touch `/etc/screenrc`: the user file extends it.

## Common failures

| Symptom | Why | What to do |
| --- | --- | --- |
| `There is no screen to be resumed` | wrong name, or the session already died | `screen -ls`, then the exact name |
| `Attached` and `-r` refuses | session still on another pty | `screen -d -r name` |
| Session vanished after a command | the only window’s process exited | wrap with `bash -lc '…; exec bash'` |
| `Ctrl-a` “eats” emacs/tmux inside | screen’s prefix wins | `Ctrl-a a` for a literal; or another escape in `.screenrc`: `escape ^Bb` |
| No vertical split | Screen &lt; 4.1 | horizontal `Ctrl-a S`, or a newer package |
| No log file | no `-L` and nobody pressed `Ctrl-a H` | enable it explicitly, check the session cwd |

Sockets live in `/run/screen/S-$USER/` or `~/.screen/`. Another user’s session is not something you attach to by accident: the directory permissions block it.

## When screen, when not

A single non-interactive background command is enough for `systemd-run --user`, `tmux`, or even `nohup`. A standing workspace on a bastion, a deploy you start and then close the lid on, shared log tailing — screen covers that without extra dependencies.

`tmux` is nicer for splits and config. Screen wins when it is **already there** on the box you just SSHed into. A named session and `Ctrl-a d` are the reason it stays in muscle memory.
