Local-first SSH fleet manager and key lifecycle tool.
Manage SSH servers, identities, key rotation, authorized_keys, bulk operations,
live terminal access, and encrypted secrets — all self-hosted, no analytics.
Go backend · PostgreSQL · React dashboard · AES-256-GCM encrypted vault · Audit trail · WebSocket terminal
ssh-fleet-manager is a local-first SSH fleet and key lifecycle manager for operators who maintain many hosts and want a small, inspectable control plane. It combines a Go backend, PostgreSQL storage, and a React dashboard for server inventory, SSH identity management, key rotation workflows, Guix inventory, and live terminal access.
The project is designed for self-hosted use. There is no hosted control plane and no product analytics pipeline. Your database, encrypted key material, runtime logs, and audit records stay in the environment where you run the app.
Security note: ssh-fleet-manager is a trusted operator console. It can hold private SSH key material and can modify remote
authorized_keysfiles. Do not expose it to an untrusted network without TLS, network-level access controls, and a deployment plan that treats the local login as only one layer.
If you manage more than a handful of SSH hosts, you probably have a spread of
keys, a ~/.ssh/config that keeps growing, and no good way to see which key is
on which host. ssh-fleet-manager gives you a single dashboard to:
- See your whole fleet — every host, its pinned host-key fingerprint, attached identities, and connection status, in one searchable inventory.
- Rotate keys safely — preview-first bulk deploy, renew, rotate, replace, and
distribute workflows that preserve unknown or manual
authorized_keyslines. - Keep keys encrypted — private key material is sealed with AES-256-GCM at rest, never stored in plaintext, and downloadable on demand.
- Audit everything — every key install, rotation, remote scan, and secret reveal writes an audit row so you can trace who did what.
- Resolve secrets locally — a loopback-only plaintext resolver turns the manager into a single source of truth for API keys, tokens, and DSNs that your local tooling can fetch without exposing values to the network.
- Run a terminal in the browser — a WebSocket-backed xterm.js shell lets you drop into any server that has an attached identity, without leaving the app.
No SaaS, no telemetry, no hosted control plane. Your keys and your data stay on your infrastructure.
- Project Status
- What It Does
- Safety Model
- Screens and Workflows
- Requirements
- Quick Start
- Configuration
- Development
- Documentation
- Roadmap
- Contributing
- Security
- License
ssh-fleet-manager is pre-1.0 software. The core workflows are usable, but operators should still treat it as early release infrastructure software:
- Review preview plans before applying any remote key operation.
- Keep backups of PostgreSQL and
SSHM_DATA_DIR, especiallymaster.key. - Test against non-critical hosts before managing production access.
- Read the security notes before binding the app outside localhost or WSL.
- Maintains a searchable inventory of SSH servers, ports, usernames, tags, and pinned host-key fingerprints.
- Generates or imports SSH identities, including encrypted OpenSSH private keys.
- Stores private key material encrypted at rest with AES-256-GCM.
- Scans local
.sshfolders and~/.ssh/configfor import candidates. - Installs public keys on remote hosts through password auth or an existing attached identity.
- Previews and applies bulk key deploy, renew, rotate, replace, and distribute workflows.
- Scans remote
authorized_keysfiles and distinguishes app-managed, adoptable, manual, and invalid entries. - Preserves unknown or manual remote keys by default.
- Opens a browser terminal over WebSocket using xterm.js.
- Captures GNU Guix profile snapshots with
guix describeandguix package -I. - Tracks snippets, port forwards, SFTP file access, and audit events.
- Manages non-SSH secrets (API keys, tokens, passwords, DSNs) in an encrypted store with a dashboard Secrets panel, and resolves them to local tooling over a loopback-only plaintext resolver.
- Creates a default
admin/sshaccount, then forces credential rotation before dashboard/API access.
ssh-fleet-manager is conservative around SSH access because a bad key operation can lock an operator out of a host.
- Bulk operations are preview-first. The app builds a plan before it mutates
any remote
authorized_keysfile. - Unknown remote key lines are preserved unless the operator explicitly adopts them after importing the matching private key.
- A key is app-managed only after it is generated or imported, named, fingerprinted, attached to a server, and active.
- Passphrase hints are labels only. They are never treated as passphrases.
- Replaced identities can be archived before retirement.
- Remote apply results and local audit rows are recorded for traceability.
The UI is an operator dashboard, not a marketing surface. The main areas are:
- Home: fleet health, quick actions, recent audit activity, and vault state.
- Fleet: server inventory, tags, SSH test, host-key pinning, key install, SFTP, port forwards, and live shell.
- Keys: identity generation, import, passphrase storage, encrypted download,
local
.sshsync, archive, and lifecycle operations. - Guix: package/profile inventory for hosts that use GNU Guix.
- Snippets: reusable commands for manual or terminal-assisted workflows.
- Secrets: encrypted store for API keys, tokens, passwords, and DSNs — masked list with kind badges and tags, add/edit form, reveal-with-confirm (audited), and rotation/last-used tracking. Values never appear in list views.
- Audit: mutating events and key lifecycle history.
- Docs: in-app operational guidance for key and fleet workflows.
- Go 1.23 or newer.
- Node.js and npm for building the web UI. CI currently uses Node.js 24.
- PostgreSQL reachable from the backend.
- OpenSSH-compatible remote servers.
- On Windows, Ubuntu-26.04 WSL is the recommended runtime path.
Clone the repository:
git clone https://github.com/push42/ssh-fleet-manager.git
cd ssh-fleet-managerCreate a local PostgreSQL role and database:
make pg-createOr create them manually:
sudo -u postgres psql -c "CREATE ROLE sshm LOGIN PASSWORD 'sshm';"
sudo -u postgres psql -c "CREATE DATABASE sshm OWNER sshm;"Build the web UI:
cd web
npm ci
npm run build
cd ..Run the backend from the repository root:
SSHM_LISTEN=127.0.0.1:42428 go run ./cmd/ssh-fleet-managerOpen:
http://127.0.0.1:42428
Database migrations run automatically on backend startup.
On first launch, the app creates a single local admin account:
username: admin
password: ssh
After signing in with those defaults, you must change the password before the dashboard unlocks. The replacement password is stored as a bcrypt hash. You can change the local username or password later from Settings.
On Windows, use the bundled WSL launcher from PowerShell:
.\Start-ssh-fleet-manager-wsl.ps1 -RebuildWebStop it with:
.\Stop-ssh-fleet-manager-wsl.ps1The launcher starts the backend inside Ubuntu-26.04, checks PostgreSQL, builds
the web UI when requested, waits for /api/health, computes the current WSL IP,
and prints the URL to open. See docs/WSL_RUNBOOK.md.
For a local background process:
bash scripts/start_local_app.sh --rebuild-webCheck status or stop it:
bash scripts/start_local_app.sh --status
bash scripts/stop_local_app.shBuild a local release binary and web assets:
make buildRun from the repository root so the backend can serve web/dist, or set
SSHM_STATIC_DIR to the directory that contains the built frontend:
SSHM_STATIC_DIR=/opt/ssh-fleet-manager/web/dist ./bin/ssh-fleet-managerThe backend reads configuration from environment variables.
| Variable | Default | Description |
|---|---|---|
SSHM_LISTEN |
0.0.0.0:42428 |
HTTP listen address. Use 127.0.0.1:42428 for local-only runs. |
SSHM_DATA_DIR |
./data |
Runtime directory for the encryption master key and local state. |
SSHM_DATABASE_URL |
unset | Full PostgreSQL URL. Overrides the split SSHM_PG* variables. |
SSHM_PGHOST |
127.0.0.1 |
PostgreSQL host when SSHM_DATABASE_URL is not set. |
SSHM_PGPORT |
5432 |
PostgreSQL port. |
SSHM_PGUSER |
sshm |
PostgreSQL user. |
SSHM_PGPASSWORD |
sshm |
PostgreSQL password. |
SSHM_PGDATABASE |
sshm |
PostgreSQL database name. |
SSHM_STATIC_DIR |
unset | Directory for built web assets. The backend also checks web/dist. |
SSHM_MASTER_KEY |
unset | Optional master secret. If unset, the app creates SSHM_DATA_DIR/master.key. |
SSHM_READONLY_TOKEN |
unset | Static read-only token for metadata-only MCP/API access (/api/servers, /api/identities, /api/secrets). Never returns values or private keys. |
SSHM_RESOLVE_TOKEN |
unset | Distinct token for the loopback-only plaintext resolver. When set, a second listener starts on SSHM_RESOLVE_LISTEN serving GET /resolve/{name}. Keep this secret in your external secret store. |
SSHM_RESOLVE_LISTEN |
127.0.0.1:42429 |
Bind address for the plaintext resolver. Keep it off the public network; the reverse proxy never proxies it. When the backend runs in WSL and the bridge runs on Windows, bind to the WSL interface or add a netsh portproxy for 42429 (see docs/ARCHITECTURE.md). |
SSHM_DEV |
0 |
Enables development metadata in health responses. |
SSHM_LOCAL_SSH_DIR |
unset | Default destination for explicit local .ssh syncs. |
SSHM_LOCAL_KEY_PREFIX |
unset | Default basename prefix for locally synced identities. |
Back up SSHM_DATA_DIR/master.key. Without the same master key, encrypted
private key material stored in PostgreSQL cannot be decrypted.
Run backend tests:
go test ./...Run frontend checks:
cd web
npm ci
npm run typecheck
npm run buildRun the Vite dev server for frontend-only work:
cd web
npm run dev -- --host 127.0.0.1The production-style app is still served by the Go backend on port 42428.
Use Vite only while actively editing React code.
- Architecture: component layout, key lifecycle, storage, and remote mutation flow.
- Contributing: setup, PR expectations, tests, and UI rules.
- WSL Runbook: Windows/WSL startup, status, port proxy, and troubleshooting.
- Visual Reference: screenshot gallery of every panel and workflow.
- Agent Guide: repository rules for coding agents and automated assistants.
- Claude Guide: concise guidance for Claude-based coding sessions.
- Security Policy: reporting and operating guidance.
- Reverse-proxy hardening recipes for remote operator access.
- Release packaging that can serve the compiled frontend without relying on a working tree.
- Scheduled key rotation.
- More complete backup and restore tooling.
- Expanded audit filtering and export.
- Hardening around multi-operator deployments.
See [issues][issues-url] for current work and proposed features.
Issues and pull requests are welcome. For large changes, please open an issue first so design, safety, and test expectations are clear.
Before opening a pull request:
go test ./...
cd web && npm run typecheck && npm run buildFor detailed expectations, see docs/CONTRIBUTING.md.
Do not open a public issue with private keys, passwords, logs containing secrets, hostnames that must remain private, or exploit details. See SECURITY.md for the reporting process.
When the dashboard is reached over the public internet (for example via a reverse proxy behind Cloudflare), three controls keep secret values safe:
- Metadata-only public surface. The main listener returns secret names,
kinds, tags, and dates — never values. Reveal is a POST that requires a
logged-in session, is audited, and is served with
Cache-Control: no-store. - Loopback-only resolver. Plaintext values are served only by a second
listener bound to
127.0.0.1:42429, gated by a distinctSSHM_RESOLVE_TOKEN. The/resolve/*route is never registered on the main listener, so it is physically absent on whatever port the proxy exposes. - Rate limiting and headers. Login and resolve are per-IP rate-limited with
lockout; both listeners apply a strict CSP,
X-Frame-Options: DENY,nosniff,no-referrer, and HSTS.
The read-only and resolve tokens are bootstrap secrets kept in an external
secret store (not the secrets table), so the manager never depends on itself
to boot. See docs/ARCHITECTURE.md for the full listener
and token model.
ssh-fleet-manager is distributed under the MIT License. See LICENSE.
SSH fleet manager, SSH key management, SSH key rotation, authorized_keys management, SSH server inventory, key lifecycle, encrypted SSH key vault, bulk SSH key operations, SSH terminal, self-hosted SSH manager, infrastructure automation, sysadmin tool, DevOps tool, Go SSH manager, PostgreSQL SSH manager, React SSH dashboard, audit trail, AES-256-GCM encryption, homelab SSH manager, local-first secret manager, loopback secret resolver, host-key pinning, xterm.js terminal, WSL SSH manager.


