Self-hosted server management — SSH, SCP, RDP, VNC & local terminal from your browser or desktop
⭐ If Termi is useful to you, please star the repo. It costs nothing, takes two seconds, and is the main way other developers find this project — more effective than a clone, since a fork or a clone doesn't show up anywhere public. See Why star instead of just cloning?
- Features
- Quick Start
- Desktop App Installation
- Development Setup
- Project Structure
- Configuration
- Architecture
- Security
- Contributing
- Open Source & Community
- Why star instead of just cloning?
- Star History
- License
- AES-256-GCM encryption for all stored server credentials
- Argon2id password hashing with secure parameters
- TOTP-based 2FA — works with Google Authenticator, Authy, and any TOTP app
- Passkey / WebAuthn — passwordless login with hardware keys and biometrics
- Google OAuth — "Sign in with Google" support
- Optional master key — adds a second encryption layer derived via PBKDF2
- Zero-trust architecture — credentials are decrypted only in memory, never stored in plaintext
- SSH — full terminal emulation with xterm.js, key forwarding, and resizable viewport
- SCP / SFTP — web-based file manager: upload, download, create folders, rename, delete
- RDP — Windows Remote Desktop via Apache Guacamole
- VNC — Virtual Network Computing via Apache Guacamole
- Access your local machine's shell directly from the app
- Electron: spawns PowerShell (Windows) or your default shell (macOS/Linux)
- Browser/cloud: spawns a shell on the gateway host (gated by
ALLOW_LOCAL_TERMINAL=true)
- Real-time CPU, memory, and disk metrics fetched over SSH
- Health history charts
- Configurable email & push notification alerts
- Built-in benchmark tool
- Share server access with other users via invitation links
- Per-server share management and revocation
- Install as a PWA on any device (iOS, Android, desktop)
- Virtual keyboard with Ctrl, Alt, Shift, Fn, and arrow keys
- Touch-optimised design
- Web push notifications for monitoring alerts
- Native app for macOS, Windows, and Linux
- Local terminal access via node-pty
- Connects to your self-hosted Termi instance
- Bundled gateway for fully offline / local-stack operation (
electron:dev:full)
- Docker Compose one-command deployment
- PostgreSQL database — your data stays on your server
- No telemetry, no cloud dependencies
- Traefik reverse-proxy support included
- Docker and Docker Compose
# 1. Clone the repository
git clone https://github.com/shuvoooo/termi.git
cd termi
# 2. Copy and configure environment
cp .env.example .envOpen .env and set the required secrets (generate each with openssl rand -base64 32):
DB_HOST=postgres # set to your DB host (Docker service name or external host)
DB_USER=termi
DB_PASSWORD=<strong-password>
DB_NAME=termi
SESSION_SECRET=<openssl rand -base64 32>
ENCRYPTION_KEY=<openssl rand -base64 32>
GATEWAY_JWT_SECRET=<openssl rand -base64 32>
NEXT_PUBLIC_GATEWAY_URL=ws://localhost:22080/gateway
NEXT_PUBLIC_APP_URL=http://localhost:22080# 3. Start the stack
docker compose up -d
# 4. Run database migrations
docker compose exec web npx prisma migrate deploy
# 5. Open Termi
open http://localhost:22080RDP / VNC: also start guacd — on Apple Silicon, add
--platform linux/arm64:docker run -d -p 4822:4822 --name termi-guacd guacamole/guacd: 1.6.0
Download the desktop app for your platform and architecture from the landing page. Builds are provided for macOS, Windows, and Linux in both x64 and arm64.
The macOS build is ad-hoc signed but not notarized (the project has no paid Apple Developer ID). On first launch, macOS Gatekeeper shows:
"Apple could not verify 'Termi.app' is free of malware…"
This is expected. To open the app anyway, use either method:
Option A — Right-click to open
- In Finder, locate Termi.app (drag it to
/Applicationsfirst). - Right-click (or Control-click) the app → Open.
- Click Open in the dialog. macOS remembers this choice for future launches.
Option B — Remove the quarantine flag
xattr -dr com.apple.quarantine /Applications/Termi.appThen open the app normally.
SmartScreen may warn that the publisher is unknown. Click More info → Run anyway.
The Linux build is distributed as an AppImage — no installation required.
chmod +x termi.AppImage
./termi.AppImage- Node.js 22+
- PostgreSQL 15+ (or use Docker Compose)
# Install dependencies
npm install
# Copy and configure env
cp .env.example .env
# Edit .env — set DATABASE_URL (or DB_HOST/USER/PASS/NAME) + secrets
# Generate Prisma client
npm run db:generate
# Push schema to database
npm run db:push
# Start both services (web :22080, gateway :22081)
npm run dev:allnpm run dev:all # Web + Gateway (recommended)
npm run dev # Web only
npm run dev:gateway # Gateway only
npm run db:migrate # Create + apply a migration
npm run db:studio # Prisma Studio database browser
npm run db:seed # Seed with sample data
npm run test # Unit tests (Vitest)
npm run test:e2e # End-to-end tests (Playwright)
npm run lint # ESLint across all workspaces
npm run build # Production build
# Desktop app
npm run electron:dev # Open Electron pointing at a running web instance
npm run electron:dev:full # Run full local stack + Electron together
npm run build:electron # Package Electron apptermi/
├ apps/
│ ├ web/ # Next.js 16 App Router
│ │ ├ src/
│ │ │ ├ app/ # Pages + API routes (App Router)
│ │ │ │ ├ api/ # 53 REST endpoints
│ │ │ │ └ panel/ # Dashboard UI
│ │ │ ├ components/ # React components
│ │ │ │ ├ terminal/ # SSH/RDP/VNC/local terminal
│ │ │ │ ├ scp/ # File manager
│ │ │ │ └ monitoring/ # Metrics & charts
│ │ │ └ lib/
│ │ │ ├ auth/ # Session, TOTP, passkey, OAuth
│ │ │ ├ crypto/ # AES-256-GCM, key derivation
│ │ │ ├ security/ # SSRF protection, rate limiting
│ │ │ └ services/ # SSH pool, SFTP, monitoring, alerts
│ │ └ prisma/ # Database schema & migrations
│ │
│ ├ gateway/ # WebSocket gateway (pure ESM)
│ │ └ src/
│ │ ├ handlers/ # SSH, SCP, Guacamole (RDP/VNC), Local PTY
│ │ └ auth/ # JWE token validation
│ │
│ ├ electron/ # Desktop app wrapper
│ │ ├ main.js # Electron main process + node-pty IPC
│ │ ├ preload.js # Secure context bridge
│ │ └ updater.js # Auto-update via GitHub Releases
│ │
│ └ mobile/ # Capacitor iOS/Android shell
│
├ .github/workflows/ # CI, release notes, desktop builds
├ traefik/ # Reverse-proxy configuration
├ docker-compose.yml
├ docker-compose.local.yml # Local development with Docker
├ electron-builder.yml # Desktop build config (authoritative)
└ .env.example
The desktop and mobile apps are thin shells around the hosted web app, so UI changes reach them without a new release. The desktop app checks GitHub Releases for shell updates on launch and every 6 hours.
| Variable | Description |
|---|---|
DB_HOST |
PostgreSQL host |
DB_USER |
PostgreSQL username |
DB_PASSWORD |
PostgreSQL password |
DB_NAME |
Database name |
SESSION_SECRET |
iron-session cookie key (≥32 chars) |
ENCRYPTION_KEY |
AES-256-GCM key for credentials at rest (≥32 chars) |
GATEWAY_JWT_SECRET |
Shared secret for JWE connection tokens (≥32 chars) |
NEXT_PUBLIC_GATEWAY_URL |
Browser-visible WebSocket URL of the gateway |
NEXT_PUBLIC_APP_URL |
Public URL of the web app |
| Variable | Default | Description |
|---|---|---|
GUACD_HOST |
localhost |
guacd host for RDP/VNC |
GUACD_PORT |
4822 |
guacd port |
ALLOW_PRIVATE_NETWORKS |
false |
Allow connections to private/internal IPs |
TRUSTED_PROXY |
false |
Trust X-Forwarded-For (enable behind Nginx/Traefik) |
ALLOWED_ORIGINS |
NEXT_PUBLIC_APP_URL |
CORS origins for the gateway |
ALLOW_LOCAL_TERMINAL |
false |
Enable local PTY terminal on the gateway host |
GATEWAY_DETACHED_TTL_MIN |
30 |
Minutes a detached SSH session is kept alive for reconnect |
GATEWAY_MAX_CONNECTIONS_PER_USER |
0 |
Concurrent sessions per user; 0 = unlimited |
GOOGLE_CLIENT_ID |
— | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
— | Google OAuth client secret |
SMTP_HOST |
— | SMTP host for email (verification, alerts, invites) |
SMTP_USER |
— | SMTP username |
SMTP_PASS |
— | SMTP password |
VAPID_PUBLIC_KEY |
— | Web push VAPID public key |
VAPID_PRIVATE_KEY |
— | Web push VAPID private key |
See
.env.examplefor the full list with descriptions.
┌─────────────────────────────────────────────────────────────┐
│ Browser / PWA / Electron │
│ Login · Dashboard · Terminal · File Manager · Monitor │
└───────────────────────────┬─────────────────────────────────┘
│ HTTP / REST
▼
┌─────────────────────────────────────────────────────────────┐
│ Next.js Web App (:22080) │
│ API Routes │ Auth │ AES-256-GCM Crypto │ Prisma ORM │
└──────────┬────────────────────────────────┬─────────────────┘
│ │
│ SQL (pg) │ POST /api/connection/token
▼ ▼
┌──────────────────┐ ┌─────────────────────────────────┐
│ PostgreSQL DB │ │ WebSocket Gateway (:22081) │
└──────────────────┘ │ JWE token validation │
└──────────┬──────────┬───────────┘
│ │
SSH / SCP RDP / VNC
│ │
┌──────┴──┐ ┌────┴──────────┐
│ SSH Host│ │ guacd :4822 │
└─────────┘ └───────┬───────┘
│
RDP / VNC Servers
Connection flow:
- Browser calls
POST /api/connection/token→ server decrypts stored credentials and issues a short-lived **JWE token ** (A256GCM, 5-minute TTL). - Browser opens a WebSocket to the gateway with the JWE token as a query parameter.
- Gateway validates the token and routes to the appropriate handler:
SSHHandler,SCPHandler,GuacamoleHandler, orLocalHandler. - For RDP/VNC,
GuacamoleHandlerconnects to guacd and forwards the Guacamole protocol frames to the browser.
See SECURITY.md for the full security policy, vulnerability reporting process, and threat model.
Highlights:
- Credentials encrypted with AES-256-GCM before database storage
- SSRF protection on all user-supplied host inputs
- Rate limiting on authentication endpoints
- CSP and security headers on every response
- Session tokens revocable per-device
Contributions are welcome! Please read CONTRIBUTING.md for guidelines on:
- Setting up the development environment
- Branching and commit conventions
- Submitting pull requests
- Reporting bugs and requesting features
- Contribution guide: CONTRIBUTING.md
- Code of conduct: CODE_OF_CONDUCT.md
- Help & support channels: SUPPORT.md
- Security policy: SECURITY.md
- Release notes: CHANGELOG.md
- Full documentation: Wiki
Cloning gets you the code. Starring is the only action that shows up publicly — it's what surfaces Termi in GitHub's trending pages, in search ranking, and in other developers' recommendations. A clone or a fork is invisible outside your own account; a star is a two-second, zero-cost signal that tells the next person searching "self-hosted SSH client" that this project is worth a look.
If you've already cloned or forked Termi and it's useful to you, this is the one extra click that actually helps the project grow:
Stars also directly inform what gets built next — they're the closest thing this project has to a roadmap signal from real users.
This project is licensed under the MIT License — see LICENSE for details.
- xterm.js — terminal emulator
- Apache Guacamole — RDP/VNC gateway
- ssh2 — SSH client for Node.js
- node-pty — local PTY for Electron
- Next.js — React framework
- Prisma — database ORM
- Tailwind CSS — utility-first CSS
- iron-session — session management
If Termi saves you time, consider supporting its development:
Made with ❤️ for the self-hosting community

