GatewayAvailable
Server (Linux) — the broker that issues certs
Short-lived SSH certificates — requested by your laptop, approved on your iPhone, signed by a YubiKey-backed CA. Nothing static to steal.
A login runs in seven steps. The OSH client asks the gateway for a certificate. The gateway sends an approval request to the approver phone, which signs it. The gateway passes the signed approval to the CA over a WebSocket the CA opened. The CA checks the approval chain, signs a short-lived SSH certificate with its YubiKey and returns it to the gateway. The gateway returns the certificate to the client, which logs in to the target server. The server trusts the CA public key it was given in advance.
Download
macOS builds are signed & notarized — open them directly. Always download through a browser to keep the signature intact.
Server (Linux) — the broker that issues certs
Just the osh command — servers / headless / scripts (no GUI)
macOS: osh is already inside the OSH - Client .pkg above.
Signs certificates with a YubiKey (PIV slot 9c)
Workstation — where you run ssh to get a cert
iOS — approves & signs requests (Secure Enclave + Face ID)
Live on the App Store — tap the button, or search “OSH Zero Trust SSH” on your iPhone. No invitation needed.
Browser — manage signers, servers, groups and rules
Sign in by scanning the code with OSH - Signer. The browser holds no trusted key: every change waits for a Secure Enclave signature from your phone, and the Audit tab records who approved what.
Compatibility
Each piece ships on its own cadence and the newest of each always works together — that is what Download above gives you. Pinned to an older build? Open a release below for a set known to work together; every link is live.
OSH - Signer is iPhone-only and always the latest App Store build — it can’t be pinned to a past version. Gateway is Linux-only (server); OSH - Client & OSH - CA ship macOS (Apple Silicon), Windows, and Ubuntu amd64.
Install
Four pieces on four machines. The Gateway is a Linux server (the broker); the OSH - Client, OSH - CA & OSH - Signer are cross-platform. Four steps, each command with its own Copy button.
One Linux server runs the broker. The installer sets up a SQLite config, generates a CA token and starts the service.
curl -fsSL https://osh.openlay.com/downloads/install.sh | sudo sh -s -- --autocert gw.example.com --autocert-email you@example.comReal TLS, and the only setup where phone approvals can ring. Point the domain's DNS A record at this box first. Ports 80 + 443 must be reachable (80 carries the Let's Encrypt check); the installer stops if the domain does not resolve here or the ports are taken. Phone approvals: add --push-relay "your-company" (HTTPS only — Open Layer approves you before anything is delivered).
curl -fsSL https://osh.openlay.com/downloads/install.sh | sudo sh -s -- --public-url http://YOUR_SERVER:8443YOUR_SERVER is the address clients reach this box at — its LAN or public IP, not localhost. No TLS, and approvals cannot ring on a phone.
sudo journalctl -u pam-zta-gateway -o cat --no-pager -n 300-o cat strips the log prefix and -n 300 shows past lines (-f alone tails only 10, cutting the QR). Widen the window if it wraps.
curl -fsSL https://osh.openlay.com/downloads/install.sh | sudo shUpgrades in place — config, CA token, database and keys are kept; the service restarts on the new version.
Turn on phone approvals while upgrading (gateway already on HTTPS):
curl -fsSL https://osh.openlay.com/downloads/install.sh | sudo sh -s -- --push-relay "your-company"Still on plain http://…:8443? The relay refuses http. Move to HTTPS first, keeping your CA token so the CA stays connected:
T=$(sudo sed -n 's/^token *= *"\(.*\)"/\1/p' /etc/pam-zta/gateway.toml | head -1)curl -fsSL https://osh.openlay.com/downloads/install.sh | sudo sh -s -- --force-config --ca-token "$T" --autocert gw.example.com --autocert-email you@example.com --push-relay "your-company"Rewrites the config (the old one is kept as gateway.toml.bak.*) and changes the gateway address — point every OSH client and phone at https://gw.example.com afterwards.
1. Install — --no-install-recommends keeps PostgreSQL out (SQLite needs no DB server).
sudo apt install --no-install-recommends ./pam-zta-gateway_0.36.0_amd64.debUbuntu / Debian
sudo dnf install --setopt=install_weak_deps=False ./pam-zta-gateway-0.36.0-1.x86_64.rpmRHEL / Rocky / Fedora
2. Point it at SQLite and set a CA token (writes the whole config).
sudo sh -c 'cat > /etc/pam-zta/gateway.toml' <<EOF
[server]
listen_addr = ":8443"
public_url = "http://YOUR_SERVER:8443" # IP or domain clients reach (NOT localhost) — goes into the enroll QR
[ca]
token = "$(openssl rand -hex 32)"
[database]
driver = "sqlite"
path = "/var/lib/pam-zta/gateway.db"
[cert]
ttl = 300
[gateway_key]
key_path = "/var/lib/pam-zta/gateway_key.pem"
EOFsudo chown root:pam-zta /etc/pam-zta/gateway.toml && sudo chmod 640 /etc/pam-zta/gateway.toml3. Start it, then scan the root-enroll QR and hand the CA token to the CA operator.
sudo systemctl enable --now pam-zta-gatewaysudo journalctl -u pam-zta-gateway -o cat --no-pager -n 300grep token /etc/pam-zta/gateway.tomlConnect to it: OSH - Client uses http://SERVER:8443 as its gateway; OSH - CA uses ws://SERVER:8443/v1/ca/ws plus the [ca] token. HA / multi-node? Use PostgreSQL instead of SQLite ([database] host/user/…).
OSH - CA on the machine that holds the YubiKey, OSH - Client on each laptop, OSH - Signer on each approver's iPhone.
OSH - CA (.dmg): drag to Applications, open, plug the YubiKey, then enter its PIN in Settings → YubiKey. The PIN stays in memory only, so OSH - CA asks for it again after every restart or re-plug. A new YubiKey has no CA key yet: see YubiKey setup.
OSH - Client (OSH-1.8.0-arm64.pkg): double-click to install — notarized, adds the OSH app and the osh command.
osh connect dev@your-serverOr open the OSH app and click Connect. New terminal? Run rehash first.
Run CA-1.9.1.exe or OSH-1.8.0.exe (code-signed). If SmartScreen still appears — the new certificate is building reputation — choose More info ▸ Run anyway.
sudo apt install ./CA-1.9.1-amd64.deb # OSH - CA (Operator Console)sudo apt install ./OSH-1.8.0-amd64.deb # OSH - Client1. Get OSH - Zero Trust SSH from the App Store (button above, or search “OSH Zero Trust SSH”).
2. Open OSH, scan the enrollment QR from the Gateway, approve with Face ID.
One command from your laptop — curl runs locally and pipes the script over ssh, so the target needs no internet.
curl -fsSL https://osh.openlay.com/downloads/install-pam-zta-ca.sh | ssh ubuntu@YOUR_TARGET 'sudo bash -s BASE64_FROM_GUI'ubuntu@YOUR_TARGET — the ssh login you already use on that server (needs sudo; root@… works too). BASE64_FROM_GUI — OSH - CA ▸ Settings ▸ Export Public Key ▸ Copy, pasted as-is (no ssh-ed25519 prefix).
It installs /etc/ssh/pam-zta-ca.pub and an sshd drop-in, validates (sshd -t), then reloads — no restart, no dropped sessions, authorized_keys untouched, safe to re-run. Expect trustedusercakeys /etc/ssh/pam-zta-ca.pub, the CA fingerprint (match it in the GUI) and INSTALL_OK.
ubuntu) and match an access rule — and port 22 must be reachable.Server 203.0.113.10, you normally ssh in as root, key copied from the GUI:
curl -fsSL https://osh.openlay.com/downloads/install-pam-zta-ca.sh | ssh root@203.0.113.10 'sudo bash -s AAAAC3NzaC1lZDI1NTE5AAAAIG43Man3DUHiddGccOJM897B/BaSm3tgSh09z3blL/hz'Two CA YubiKeys for redundancy (either one signs)? Pass both keys — the target trusts both:
curl -fsSL https://osh.openlay.com/downloads/install-pam-zta-ca.sh | ssh root@203.0.113.10 'sudo bash -s BASE64_CA1 BASE64_CA2'Undo:
sudo rm -f /etc/ssh/sshd_config.d/50-pam-zta.conf /etc/ssh/pam-zta-ca.pub && sudo systemctl reload sshFirst time on a gateway, register this laptop; an admin approves it once in the App Signer.
export OSH_GATEWAY=https://your-gateway:8443Add to ~/.bashrc to skip --gateway every time.
osh enrollRegisters this device.
osh statusOptional: expect status:ok, ca_connected:true.
osh root@10.0.0.5Short for osh connect root@10.0.0.5. The login user must match an access rule (e.g. root — not just any name).
The session is recorded to ~/.osh/sessions/; the cert expires in minutes, so nothing static is left behind.
| 401 | Device not enrolled on this gateway — re-run osh enroll |
|---|---|
| 403 | Device not approved yet, or the login user doesn't match a rule |