Simple · Secure · Self-hosted

Your entire server fleet, behind one browser tab.

Bastion is an infrastructure gateway that turns scattered .pem files and one-off SSH sessions into a single governed entry point, secure, browser-based access, file transfer, and full session recording, with no private key ever touching a laptop.

NODE.JS 22+POSTGRESQL 16+DOCKER COMPOSEASCIICAST v2
Bastion — session · prod-web-03
connected
recorded session · asciicast v2
00:34 / 01:12
connectfile transfer markedreplay ready
$|

Server access today is a mess of half-measures.

Every workaround solves one part of the problem and quietly creates three more. Bastion exists to replace the whole pile with one governed entry point.

~/var/log/keys.log

Keys go everywhere but stay tracked nowhere

.pem files get emailed, Slacked, and copied into a dozen ~/.ssh folders. No one can say for certain who still has access to what.

~/var/log/transfer.log

File transfers scatter across tools

Desktop SFTP clients and one-off scp commands move files with no shared record of what went where, or when.

~/var/log/incident.log

2 AM incidents come with no replay

When something breaks, there's no way back to what actually happened, just guesswork about what someone typed.

~/var/log/access.log

Revocation is a hope, not a guarantee

Offboarding someone rarely means their access ends immediately. Old keys and open sessions tend to outlive the person who held them.

$|

Built for anyone who still opens a terminal to reach a machine.

Managing a cloud fleet

For a DevOps engineer running dozens of production hosts, Bastion replaces per-machine key sprawl with one workspace: every connection routed, every credential encrypted at rest, every session available for security review, without another terminal emulator profile to maintain.

Running a homelab

For anyone administering a handful of Raspberry Pis and a NAS, Bastion turns a phone or laptop browser into a full terminal, reachable over a private network overlay, with no local SSH config to keep in sync across devices.

$|

Everything routed through one governed entry point.

Each of these ships as the default, not an add-on configured after the fact.

A real terminal that holds its place

Full xterm.js SSH access from any device, phone included, with a purpose-built mobile input layer. Close the tab, lock the phone, drop the WiFi, the session is exactly where it was on reconnect.

Nobody ever sees a credential

Server passwords and private keys stay encrypted and are never exposed to the people using them. Access goes to servers, not to secrets.

Every session, recorded and replayable

Terminal sessions are captured automatically in a compact, open format and replay frame-by-frame, file transfers marked directly on the timeline. No setup, no opt-in.

File transfer without leaving the terminal

Drag a file onto the terminal and it streams straight to the remote host, no disk buffering, no separate FTP client, no context switch. It shows up in the audit trail like everything else.

Workspaces and roles that mean something

One workspace per deployment, three roles. Owners and admins manage servers and users and can review anyone's session history; everyone else sees only what they've been given.

Revocation that's actually immediate

Pull someone's access and any terminal session they had open is severed on the spot, not next time they reconnect, not eventually.

$|

Locked down by default, not by configuration.

Follow one request from a browser tab to the server it reaches and what happens if it gets further than it should.

01 · from anywhere

The request leaves the browser.

There's no local SSH config, no key on disk, no terminal emulator profile to keep in sync. Just a tab, pointed at a private network address.

02 · the only door

A reverse proxy is the sole entry point.

Reachable only from a private network overlay, a personal VPN mesh works well. TLS terminates here, forwarding Upgrade and Connection headers for the WebSocket terminal.

03 · never on the internet

Bastion itself stays on loopback.

Bound to 127.0.0.1 on the host by default, not reachable from the network or the internet until something is deliberately put in front of it.

04 · worst case, still contained

The runtime assumes it will be reached anyway.

Unprivileged non-root user, read-only root filesystem, every Linux capability dropped. A compromise has almost nothing to work with.

Browser

You, Anywhere

Reverse proxy

Private Network

Bastion

127.0.0.1

Unprivileged, non-root user
Read-only root filesystem
All Linux capabilities dropped
Two writable paths only: recordings, logs

First launch

Bastion creates a single owner account automatically and requires a real name, email, and password before anything else is possible, no shared default credentials left sitting in production.

$|

A modular monorepo, no unnecessary abstraction.

Turborepo and pnpm workspaces tie it together, plain, explicit choices at every layer instead of a framework for the sake of one.

Frontend
ReactViteTypeScriptTailwind CSSshadcn/uiTanStack QueryReact Router
Backend
Node.js 22+ExpressTypeScriptZod I/O validationssh2busboy
Database
PostgreSQL 16+raw pg queriesnode-pg-migrateno ORM
Terminal & recording
xterm.jsasciicast formatasciinema player
Security
Argon2id password hashingSHA-256 session tokensparameterized SQL
Deployment
Docker Composehardened non-root imageread-only container
$|

Running in minutes. No source, no build step.

The install script pulls the deployment files needed, generates secure random values where it safely can, and gets out of the way.

curl -fsSL https://bastion.domain.in/install.sh | sh

Building from source instead? To swap the logo, adjust the theme, or extend functionality, just means cloning the repository and running it with hot-reloading for local development.

Run the install script

Pulls the deployment files needed and generates secure random values where it safely can.

Fill in the .env

cd bastion, then edit .env for anything still left blank.

docker compose up -d

Bastion connects to its database, brings the schema up to date, and starts serving.

Set the real owner password

Open the URL, log in with the bootstrap credentials, and you're managing servers.

$|

What's next...

These are open ideas for where Bastion goes from here, not shipped features. Contributions toward any of them are welcome.

01

Session sharing

Share a recorded session via a unique link for debugging, onboarding, or post-mortems.

02

Live session shadowing

Let an admin silently observe an active session in real time.

03

Zero-downtime key rotation

Rotate the encryption key across every stored credential atomically, with automatic rollback.

04

SSO / OAuth

Google and GitHub login for faster team onboarding.

05

SSH host fingerprint verification

Cryptographic host verification to guard against man-in-the-middle attacks on first connection.

06

Cloud storage offload

Move session recordings to S3 or Azure Blob Storage instead of local disk.

07

Command snippet library

Store and inject frequently-used scripts directly into the terminal.

08

Zmodem support

Legacy in-terminal file transfer protocol support.

MIT licensed · self-hosted · no vendor lock-in

One governed entry point.
Zero credentials on anyone’s laptop.