Operator documentation
Hyperion docs
Everything to install, run and script a Hyperion node or cluster. Grounded in the project README and operator runbook β source on GitHub.
Introduction
Hyperion is a self-hosted, multi-node hosting control panel written in Rust. One binary on each server, one web UI on the master. It provisions PHP / static / Node.js sites end-to-end β nginx + FPM pool + database + TLS + WordPress β in a single atomic transaction, and manages a fleet of VPSes from one screen.
Two layers per box
hyperion-agentβ runs as root, owns all system state (users, dirs, nginx vhosts, FPM pools, DBs, certs, FTP, cron, backups). Listens on a local Unix socket/run/hyperion.sock(mode 0660, grouphyperion-admin). On worker nodes it also listens on0.0.0.0:9443for signed RPC from the master.hyperion-webβ master only. axum + askama + HTMX, runs unprivileged in thehyperion-admingroup. Owns the audit log, web users, sessions ledger, enrolled-nodes registry and the Ed25519 master signing key.hctlβ a thin CLI over the same Unix socket, plushctl remoteover the HTTP API.
Install
One-liner β fresh Debian 12+ VPS, as root
In ~3β5 minutes the script apt-installs nginx + MariaDB + PostgreSQL + PHP 8.3, installs Rust if missing, builds Hyperion from source, lays down /etc/hyperion/{agent,web}.toml, installs systemd units, starts both services and prompts for an admin password.
curl -fsSL https://raw.githubusercontent.com/nechodom/hyperion/main/packaging/install/install-master.sh \
| sudo bash
sudo usermod -aG hyperion-admin "$USER" # log out / in, then visit https://<host>:8443Add a worker node (cluster mode)
In the web UI: Nodes β Generate invite, copy the printed curl β¦ | sudo bash β¦ command and paste it on a fresh Debian 12+ VPS. The node enrolls within ~30 seconds and appears in the Nodes table.
In-place updates
sudo /opt/hyperion/packaging/install/update.sh
# or, from the web UI: /install β row for the node β Updateβ¦The script stops services, fast-forwards /opt/hyperion, rebuilds, reinstalls binaries, refreshes systemd units only if they changed, and tails journalctl on a health-check failure. Schema migrations are compiled into the binary and applied on startup.
Local development (macOS / dev VPS)
git clone https://github.com/nechodom/hyperion
cd hyperion
cargo build --release --workspace
# binaries land in target/release/{hyperion-agent,hyperion-web,hctl}Configuration
The installer writes these for you; this is the manual reference. First lay down the group, directories and the agent config.
sudo groupadd --system hyperion-admin
sudo usermod -aG hyperion-admin "$USER" # log out / back in to pick up
sudo install -d -m 0700 /etc/hyperion
sudo install -d -m 0700 /etc/hyperion/secrets
sudo install -d -m 0700 /var/lib/hyperion
sudo install -d -m 0750 /var/log/hyperion
sudo install -d -m 0755 /var/lib/hyperion/acme-challenges/etc/hyperion/agent.toml
[agent]
socket_path = "/run/hyperion.sock"
socket_group = "hyperion-admin"
state_db = "/var/lib/hyperion/state.db"
secrets_dir = "/etc/hyperion/secrets"
log_path = "/var/log/hyperion/agent.log"
home_root = "/home"
[acme]
directory_url = "https://acme-v02.api.letsencrypt.org/directory"
contact_email = "you@example.com" # required for LE
challenge_dir = "/var/lib/hyperion/acme-challenges"systemd unit
The agent runs as root through systemd, hardened with NoNewPrivileges, ProtectSystem=full, PrivateTmp and friends.
[Service]
Type=simple
ExecStart=/usr/sbin/hyperion-agent --config /etc/hyperion/agent.toml
Restart=on-failure
RestartSec=3s
NoNewPrivileges=true
ProtectSystem=full
ProtectKernelTunables=true
PrivateTmp=true
LogsDirectory=hyperion
RuntimeDirectory=hyperionsudo systemctl daemon-reload
sudo systemctl enable --now hyperion-agent
sudo systemctl status hyperion-agent/run/hyperion.sock (group hyperion-admin). Add your user to that group to talk to it without root.Hostings
A hosting is created in one transaction: Linux user, PHP-FPM pool, MariaDB / Postgres DB, nginx vhost and a self-signed cert. Failure at any step rolls back the rest on a LIFO stack β no orphan rows, no zombie users.
$ hctl hosting create example.com --php 8.3 --db mariadb
β created example_com (id=01K4Zβ¦)
root: /home/example_com/example.com/htdocs
db: lm_a8c_examplecz (user=lm_a8c_u, password=Hx9kβ¦RnG2)
cert: issuer=self-signed, not_after=2027-06-01- PHP 8.1 / 8.2 / 8.3 / 8.4 side by side via deb.sury.org. Static-only sites, and a reverse-proxy mode for Node.js / Python / Docker.
- Suspend / resume β 503 page, FPM stop, DB lock, user processes killed. Fully reversible.
- Let's Encrypt β HTTP-01 one-click + auto-renewal, and DNS-01 wildcard (
*.domain) via guided manual TXT or a Cloudflare API token. - Quotas β kernel-level disk quota via
setquota, per-poolmemory_limit, monthly bandwidth alerts. - Per-hosting controls β HTTP basic auth, HSTS presets, a validated custom nginx snippet, FastCGI page cache, per-hosting Redis object cache,
.user.iniphp override, live log tail. - WordPress β plugin/theme manager via wp-cli, Wordfence vulnerability scan, staging β push-to-prod with a pre-push safety backup.
/jobs/<id> page. Close the tab and the work keeps running; an orphan reaper fails anything a restart interrupts.Backups
- Local β tar.gz of htdocs +
mysqldump/pg_dump+ JSON manifest under/var/lib/hyperion/backups. - Off-site S3 + age encryption β multi-target (Wasabi / Backblaze B2 / Minio / AWS), per-target retention (daily / weekly / monthly). Client-side
ageencryption keeps the private key off the node. - Granular restore β full, database-only (roll back a bad plugin update without touching media) or files-only. Restore any archive as a brand-new domain.
Foundation-level manual backup of the state DB + secrets:
sudo tar -czf /root/hyperion-backup-$(date +%F).tar.gz \
/etc/hyperion /var/lib/hyperionMulti-node cluster
The master holds the web UI, audit log and enrolled-nodes registry. Workers run an agent the master drives over an Ed25519-signed RPC channel β a signed envelope over self-signed HTTPS on port 9443. Integrity comes from the signature, not the TLS; there's no DNS dependency between nodes (IP-based).
- Auto-placement β pick
β autoon create and the master scores every node (load + memory + hosting count) and picks the best fit. - One-click migration β move a hosting between any two nodes with live progress and a version preflight (catches a stale worker before cryptic failures). Source is suspended, not deleted, so you can verify first.
- Cross-node clone β duplicate
example.comasstaging.example.comon a different node in two clicks. - Remote node update from the master UI β apt + Hyperion rebuild runs on the worker, log streams into the panel.
- Test-node mode β test hostings get auto-subdomains and
blog_public = 0; prod hostings refuse to land there.
Panel import (migrate in)
Move existing sites off another panel without a weekend of manual work. Wizard at /import (admin only), or hctl hosting import-panel.
- Sources: HestiaCP and CloudPanel. Reads the source panel's own state directly β Hestia's flat
*.conffiles, CloudPanel's SQLite store. No scraping, no API. - In-place or remote. Remote imports from another machine over SSH: give Hyperion the host + a private key, files come across with
rsyncand databases are dumped overssh. The key is used for that one run, written0600, then deleted β never stored. - Dry-run first. A plan shows exactly what would be created / skipped / conflict before anything is touched. An existing domain is skipped, never overwritten.
- WordPress included. Files copied, DB dumped and restored,
wp-config.phprepointed at the new credentials automatically.
REST API
A Bearer-authenticated /api/v1 mirrors the panel as JSON. Every OpenAPI path is generated straight from the handlers, so the contract can't silently drift.
Authentication
Send an API key as a Bearer token. Keys are shown once at creation and stored only as a hash.
curl -H "Authorization: Bearer hyp_β¦" \
https://master.example.com:8443/api/v1/hostings- Scoped, owner-clamped keys β a capability bitmask + tenant scope, clamped to β€ the owner at mint and re-clamped on every call. Demote a user and its keys shrink instantly.
- Per-key IP allowlist (CIDR) and a per-key rate limit β over budget returns
429with aRetry-After.
OpenAPI & docs
- Machine-readable contract:
GET /api/v1/openapi.json(unauthenticated). - Rendered docs UI:
/api/v1/docsβ self-hosted, no SaaS, no upload.
Example β create a hosting
Slow actions return a job id; poll it for progress.
$ curl -X POST https://master.example.com:8443/api/v1/hostings \
-H "Authorization: Bearer hyp_β¦" \
-d '{"domain":"new.example.com","php":"8.3","db":"mariadb"}'
{ "job_id": "01K5β¦", "poll": "/api/v1/jobs/01K5β¦" }Endpoint reference
All paths are prefixed with /api/v1.
CLI β hctl
A thin client over the same Unix socket as the web UI β the "ssh in and poke" path when a node is too broken for the web to help.
$ hctl info
agent: master.example.com version=v1.2.0-5-gf718fd1 schema=54 hostings=12
$ hctl hosting suspend example.com --reason="payment overdue"
β suspended
$ hctl hosting backup-now example.com
β backup 17 ok
$ hctl audit --limit 5Off-box β hctl remote
Drive any master over the /api/v1 HTTP API with the same verbs β point it at a URL and an API key instead of the local socket.
Security
#![forbid(unsafe_code)]in every crate β the compiler enforces it.- Argon2id passwords at OWASP parameters; constant-time secret & username compare on every login and heartbeat.
- Ed25519 session cookies with a DB-backed revocation ledger β kill a stolen cookie from
/settings/sessions. - TOTP 2FA enforced for admin+ roles, with backup codes and a 30-day remember-device option.
- Native brute-force protection β the agent scans access logs for wp-login / xmlrpc floods and auto-bans IPs via an
nftablesset; bans survive reboots. - Tamper-evident audit log β a BLAKE3 hash chain over every state change, verified on agent startup (a broken chain refuses to start).
- CSP + HSTS + X-Frame-Options + Permissions-Policy + Referrer-Policy on every response; per-form CSRF tokens.
sudo sqlite3 /var/lib/hyperion/state.db \
"SELECT ts, action, result FROM audit_log ORDER BY id DESC LIMIT 20"Troubleshooting
- Permission denied on socket β make sure your user is in
hyperion-adminand you've started a new shell since theusermod -aGchange. useradd: not in sudoersβ the agent is meant to run as root through systemd. If running from a shell, prefix withsudo.- nginx fails
nginx -tβ the agent restores the backup vhost on failure; checkjournalctl -u hyperion-agent -e. - MariaDB socket auth β Debian default uses unix_socket auth for root. If you changed it, add credentials to
/root/.my.cnf.
Logs
# structured agent log (JSON Lines)
sudo tail -f /var/log/hyperion/agent.log
# service journal
journalctl -u hyperion-agent -eManual production deploy, MariaDB hardening, removal and more.