termal.in

← Blog

Record and audit SSH sessions on a bastion host

· Termalin team sshbastionsession-recordingsecurityaudit

If your network follows the standard pattern — one hardened gateway exposed to the world, everything interesting on a private network behind it — then you already have something rare in infrastructure: a single point where every privileged session passes through. That’s a security win, and it’s also why the audit question lands on the bastion’s doorstep. When someone asks “who touched the database server last Tuesday, and what did they run?”, the honest answers all involve recording sessions somewhere along that funnel. The question is where.

There are three workable architectures, and they fail in different ways. Let’s take them in turn, then get concrete.

Why the bastion concentrates the problem

A bastion exists to be the one door. Nobody reaches db.internal without passing through it — whether they hop the old-fashioned way or tunnel through cleanly with ProxyJump. That means the bastion is the one place with a complete view of who came in, when, and from where — and, if you record there, what they did.

The flip side: because everything funnels through it, a gap in bastion-side auditing is a gap in everything. And because it’s a shared, internet-facing machine, it’s also precisely the box you should assume will eventually be attacked — which matters for where recordings are stored and who could tamper with them.

The three architectures

(a) Server-side, on the bastion. Wrap every login shell in a recorder — tlog is the standard open-source choice, pam_tty_audit/auditd the kernel-level one, and commercial jump-server gateways bundle the same idea with a management UI. The strength is coverage: everyone who passes through is recorded, whatever client they use, including the contractor with a fifteen-year-old PuTTY build. The costs are operational — you now run a recording pipeline on a critical box, size its storage, ship its output somewhere safe, and keep the wrapper from ever breaking logins — plus a subtle risk we’ll come back to: server-side tooling can capture input, and input is where passwords live.

(b) Client-side, with the operator. The SSH client records the session as it happens. The recording travels with the operator — it works identically whether the target is behind this bastion, a different one, or none at all — and the client can enforce the one rule that matters most: record output only, never keystrokes. The honest limit: it covers the people who use that client. It cannot testify about someone else’s session.

(c) Both. Not redundancy for its own sake — the two layers answer different questions. The server-side layer proves nobody got through unrecorded. The client-side layer gives each operator (and each team) a searchable, replayable record of their own work that survives even if the bastion is rebuilt or compromised. When an incident review happens, having two independently produced recordings of the same session is exactly the kind of corroboration auditors like.

A practical tlog sketch for the bastion

The minimal server-side setup on a RHEL-family bastion:

dnf install tlog

# make the recorder the login shell for audited users
usermod -s /usr/bin/tlog-rec-session deploy

tlog-rec-session launches the user’s real shell underneath itself and journals the session as JSON events. Its config lives in /etc/tlog/tlog-rec-session.conf — which shell to run, where to write ("writer": "journal" sends everything to systemd-journald), and which streams to capture. On RHEL, the SSSD session-recording integration can flip the shell centrally for chosen users or groups instead of usermod one account at a time.

Playback comes with the package:

journalctl -o verbose | grep TLOG_REC=     # list recording ids
tlog-play -r journal -M TLOG_REC=<id>      # replay with original timing

Three operational notes that separate a demo from a deployment:

  • Ship the recordings off the box. A recording that lives only in the bastion’s local journal is one compromised root account away from gone. Forward journald to a remote collector (or point tlog at Elasticsearch) so the audit trail doesn’t share fate with the machine it audits.
  • Keep a breakglass account with a real shell. If the recorder binary ever breaks — bad update, full disk — every wrapped user is locked out. One unwrapped admin account, tightly controlled, saves you a console session at the worst moment.
  • Verify input capture is off. Check that log.input is false in the tlog config, and think hard before enabling pam_tty_audit broadly. Which brings us to the trap.

The keystroke trap

A terminal session has two streams, and they are not symmetric. Output contains every command that ran (the shell echoes them) plus everything those commands printed — the complete forensic record. Input adds exactly one thing output doesn’t have: the characters that were deliberately not echoed. Sudo passwords. Passphrases. The token pasted into a login prompt.

Record input on a bastion and you haven’t built an audit trail — you’ve built a keylogger with excellent coverage, aimed at your own team, whose archive is now the most valuable file in the company. This deserves its own discussion, and it has one: how to record SSH sessions without keylogging yourself. The one-line version: record output, never input, in every layer of the stack.

“Audit” is more than a pile of recordings

An auditor’s questions are rarely “show me a replay.” They’re who, when, from where, for how long, and can I trust this record. Raw session captures answer none of that by themselves. The recording layer needs:

  • Identity attached to every session. Which means per-person accounts on the bastion, not a shared admin login. A perfect recording of “someone” is a poor exhibit.
  • Connection metadata — source IP, device, timestamps — correlated with the session content, not scattered across three log files with drifting clocks.
  • A retention policy decided up front. Recordings contain config files someone catted and database rows someone selected. “Keep forever” is a liability, not a virtue; pick a window and prune.
  • Tamper resistance. A recording the recorded user can edit or delete proves nothing. Root-owned journald helps; shipping off-box quickly helps more; storage the bastion itself can’t rewrite is the goal.

Agents through the bastion

A newer wrinkle: AI agents now open sessions to internal machines, and they traverse the bastion the same way people do — via jump-host routing, never by parking credentials on the gateway. (If anything on your bastion still relies on forwarded agents, read why that’s dangerous first.)

For auditing, agents raise the bar rather than lowering it. A summary written by the agent is not a record; the record has to be made outside the agent, and agent-issued commands need to be labeled in it — so “the agent did this, I did that” is a filter you apply, not a reconstruction you attempt at 2 a.m. Server-side recorders can’t make that distinction; they see a session, not who was driving it. This is one place the client-side layer isn’t just a complement but the only layer that can capture the fact at all.

How Termalin fits

Termalin is the client-side layer: sessions are recorded output-only by design — keystrokes are never captured — with a per-host toggle and a retention window that prunes old recordings automatically. Because recording happens at the client, a session routed through a jump host is recorded like any other, and the audit log keeps the who/when/from-where alongside it: host, time, device and IP. Commands issued by an AI agent are marked as the agent’s in both the recording and the audit log, and sessions an agent is driving are mirrored live in the watch grid. On paid plans, recordings sync end-to-end encrypted — the server stores ciphertext it can’t replay, while you can replay from the web cabinet anywhere. Pair it with tlog on the bastion when you need the “everyone, no exceptions” guarantee, and you have architecture (c) without running the risky parts yourself. The same trade-offs apply on cloud fleets, with an extra twist — see session recording on EC2.


Termalin is a free, cross-platform SSH client with output-only session recording, replay and jump-host support — download it, or see session recording in detail.

Try it on one host.

Termalin is a fast SSH client for you — and your agents.

Free tier · 14-day Pro trial · pricing