Version: 1.1.3 ย ย |ย ย Updated: 2026-07-01
Packages:.deb(Ubuntu/Debian) and.rpm(RHEL/Rocky/Alma/Amazon Linux/Fedora)
Verified on: Ubuntu 22.04 LTS, Rocky Linux 9 (RHEL 9 / Alma 9 compatible)
Authyo RDP Guard on Linux adds an OTP challenge to SSH (and any
PAM-aware service you choose). After install, every interactive login
requires the user's normal credentials and a one-time code delivered
to a pre-configured admin contact โ with offline break-glass options
(Google Authenticator / backup codes) so you are never locked out.
It ships as a PAM module (pam_authyo.so) plus two helpers: an
interactive setup CLI (authyo-configure) and an out-of-band
heartbeat service (authyo-gatekeeper-heartbeat, run by a systemd
timer) that keeps licensing, the remote kill-switch, and optional
server-authoritative policy up to date.
โ ๏ธ Golden rule: always test in a NEW terminal before you log out.
Runsudo authyo-configure --testand open a second SSH session to
confirm you can still get in. If something is wrong, your existing
session is your safety net.
authyo-configure before it will write a config.app.authyo.io:443 from the server.sudo)..deb (Ubuntu / Debian) โ recommendedDownload authyo-gatekeeper_1.1.3-1_amd64.deb from the RDP Guard
section of your Authyo portal, then:
sudo dpkg -i authyo-gatekeeper_1.1.3-1_amd64.deb
# If dpkg reports missing dependencies:
sudo apt-get install -f -y
# Configure interactively (activates your license, writes the config,
# hooks pam_authyo into /etc/pam.d/sshd after backing it up)
sudo authyo-configure
# TEST BEFORE LOGGING OUT
sudo authyo-configure --test
.rpm (RHEL / Rocky / Alma / Amazon Linux / Fedora)Download authyo-gatekeeper-1.1.3-1.x86_64.rpm from your portal, then:
sudo dnf install -y ./authyo-gatekeeper-1.1.3-1.x86_64.rpm
# On older RHEL: sudo yum install -y ./authyo-gatekeeper-1.1.3-1.x86_64.rpm
# (Use dnf/yum, not `rpm -i` โ they resolve dependencies; rpm alone does not.)
sudo authyo-configure
sudo authyo-configure --test
The package registers the heartbeat timer with systemd on install
(it does not start enforcing anything until you activate a license and
configure contacts).
For an unsupported distro or to inspect/modify the build.
# Ubuntu / Debian build deps
sudo apt update
sudo apt install -y build-essential libpam0g-dev libcurl4-openssl-dev \
libcjson-dev libsqlite3-dev libssl-dev libcmocka-dev
# RHEL / Rocky build deps
sudo dnf install -y epel-release
sudo dnf install -y gcc make pam-devel libcurl-devel cjson-devel \
sqlite-devel openssl-devel systemd-rpm-macros
# Build all three binaries (pam_authyo.so, authyo-configure, heartbeat)
git clone https://github.com/techsmsidea/Authyo.git
cd Authyo/authyo-gatekeeper/linux
make
sudo make install # installs binaries + systemd units
sudo authyo-configure
sudo authyo-configure --test
To produce packages yourself instead of make install:
./packaging/build-deb.sh # on Ubuntu/Debian
./packaging/build-rpm.sh # on RHEL/Rocky/Fedora (needs rpmbuild)
authyo-configure)Running sudo authyo-configure walks you through:
https://app.authyo.io).Then sudo authyo-configure --test sends a real OTP through your first
contact, verifies the response (and its signed JWT), and probes the
backup-code endpoint โ all without logging you out.
The wizard sets up per-user routing by default. This changed from the
original release โ read this so contacts behave the way you expect.
routingMode decides which contact:
PerUser (what the wizard writes): the OTP goes to the contactname matches the Linux username logging in (case-insensitive;DOMAIN\ is stripped). A contact named admin orAdministrator is the catch-all fallback if no name matches. Givename.CentralAdmin: every login goes to the full contact listroutingMode.contactPolicy (PrimaryOnly / RoundRobin / AllInOrder) isR to resend (throttled byresendCooldownSeconds, default 30s). Each resend auto-advances to theFour ways to get in when the normal OTP can't be delivered:
| Method | Works offline (Authyo down)? | How |
|---|---|---|
| Google Authenticator (TOTP) | โ Yes โ verified locally, no network | Enrol during authyo-configure; press G at the prompt |
| Backup code | โ No โ needs Authyo reachable | Generate in the Authyo dashboard โ "Backup codes (RDP Guard only)"; press B and type it |
| Exempt (bypass) user | โ Yes | Add to exemptUsers โ that user logs in with credentials only, no OTP |
| Trusted device window | โ Yes if that IP+user logged in recently | Set trust.deviceHours (0โ24) |
B / G prompts only appear AFTER the paid OTP path fails โ thislastUsedStep) โ a code can't be replayed. It's the only path thatXXXX-XXXX-XXXX.Run sudo authyo-configure --diagnostics to grade your recovery
posture: PASS if a genuine offline path exists (TOTP or trust window),
WARN if recovery is online-only, WARN if none. It's advisory (it
does not block), but it makes the trade-off explicit.
The one true lock-out is: Authyo unreachable and no TOTP and
no trust window and no exempt user. Prevention beats recovery โ enrol
TOTP for at least one admin. If it happens, recover via an existing SSH
session, the cloud serial console, or single-user mode (see
Recovery).
Set logging.webhookUrl and every log line is also POSTed (fire-and-forget)
to your endpoint. When many servers share one webhook URL, each payload now
carries the server's identity so you can filter by it:
{ "ReportingStatus": {
"Subject": "PamGuardLog",
"ServerHost": "db-prod-01",
"LicenseKey": "RDPG-XXXX-XXXX-XXXX-XXXX",
"Timestamp": "2026-07-01T14:23:45+05:30",
"Level": "INFO",
"Message": "user=naveen ip=10.0.0.5 GATE_PASS",
"Raw": "2026-07-01T14:23:45+05:30 INFO user=naveen ip=10.0.0.5 GATE_PASS"
} }
ServerHost is the hostname (automatic โ no config).LicenseKey is the server's licence key (omitted until activated).Subject is PamGuardLog on Linux and RdpGuardLog on Windows, soThe on-disk log is always the source of truth; a slow/down receiver never
delays a login.
authyo-gatekeeper-heartbeat runs via a systemd timer โ 2 minutes
after boot, then every 15 minutes (persistent, with a small randomized
delay to avoid a fleet thundering-herd). The PAM login path only reads
the cached state, so a heartbeat outage never stalls logins. Each run:
Check it:
systemctl status authyo-gatekeeper-heartbeat.timer
systemctl list-timers | grep authyo
journalctl -u authyo-gatekeeper-heartbeat -n 50
Remote kill-switch: an admin can disable this server from the
portal. On the next login the gate fails closed and denies (log marker
GATE_DISABLED) until re-enabled. This is distinct from a license lapse โ
a lapsed/suspended license still enforces OTP but never blocks login. The
flag is cached from the heartbeat, so it survives a brief outage.
Server-authoritative policy (optional, off by default): when
serverAuthoritative is on, exempt/trust short-circuits are only honored
if the cached policy is fresh (heartbeat younger than 24 h); a stale policy
forces full OTP (or fails closed per blockOnAuthyoUnreachable). This stops
an admin from bypassing by editing the local file offline. Ship it off
unless you specifically need it.
| Path | Contents |
|---|---|
/usr/lib/x86_64-linux-gnu/security/pam_authyo.so (Debian) | The PAM module sshd loads |
/usr/lib64/security/pam_authyo.so (RHEL) | Same, RHEL convention |
/usr/sbin/authyo-configure | Interactive setup CLI |
/usr/sbin/authyo-gatekeeper-heartbeat | Out-of-band heartbeat binary |
/lib/systemd/system/authyo-gatekeeper-heartbeat.{service,timer} | The 15-min heartbeat timer |
/etc/authyo/gatekeeper.conf (mode 0600, root) | Your config โ written by authyo-configure |
/var/lib/authyo-gatekeeper/ (mode 0700, root) | Trust-device SQLite cache + state |
/var/log/authyo/gatekeeper.log (dir mode 0750) | Runtime log |
/etc/pam.d/sshd.authyo-backup-<timestamp> | Backup of your sshd PAM stack (made before editing) |
The PAM hook added to /etc/pam.d/sshd looks like:
auth required pam_authyo.so config=/etc/authyo/gatekeeper.conf
sudo authyo-configure # interactive setup (default)
sudo authyo-configure --test # send a real OTP and verify (do this before logout!)
sudo authyo-configure --status # show current config + sshd hook status
sudo authyo-configure --diagnostics # grade break-glass / recovery posture
sudo authyo-configure --revoke USER # remove a user's offline TOTP enrolment
sudo authyo-configure --uninstall # remove pam_authyo from /etc/pam.d/sshd (restores backup)
authyo-configure --version
authyo-configure --help
/etc/authyo/gatekeeper.conf)Written by authyo-configure (mode 0600, root). You don't normally hand-edit
it, but for reference the key settings are:
| Key | Default | Notes |
|---|---|---|
authyo.clientId / authyo.clientSecret | (required) | Secret is plaintext in this 0600 root-owned file |
authyo.apiEndpoint | https://app.authyo.io | |
authyo.timeoutSeconds | 10 | 1โ60 |
contacts[].name / .to / .authway | (โฅ1 required) | name is the PerUser match key |
policy.routingMode | PerUser (wizard) | CentralAdmin | PerUser |
policy.contactPolicy | PrimaryOnly | ignored for delivery โ back-compat only |
policy.expiry | 300 | OTP validity seconds (min 60) |
policy.otpLength | 6 | 4โ9 |
policy.blockOnAuthyoUnreachable | true | true = fail-closed (deny if Authyo unreachable). false = fail-open. |
policy.resendCooldownSeconds | 30 | 0โ300; min seconds between resends |
policy.backupCodesEnabled | true | offers [B] at the prompt |
policy.exemptUsers[] | [] | usernames that skip OTP (credentials only) |
policy.serverAuthoritative | false | enable server-signed policy enforcement |
trust.deviceHours | 0 | 0 = always require OTP (most secure); max 24 |
trust.cachePath | /var/lib/authyo-gatekeeper/trust.db | |
logging.level | info | debug | info | warn | error |
logging.path | /var/log/authyo/gatekeeper.log | |
logging.webhookUrl | (empty) | set to mirror every log line to your endpoint |
logging.webhookTimeoutSeconds | 5 | 1โ30 |
totp[] | [] | offline TOTP enrolments (written by the wizard / --revoke) |
license.* | โ | written by activation; includes disabled (kill-switch) and lastPolicyVersion (anti-rollback), both refreshed by the heartbeat |
/var/log/authyo/gatekeeper.log (and the webhook, if set) use these:
GATE_START ยท SENDOTP_OK / SENDOTP_RESEND_OK / SENDOTP_RESEND_FAIL /
SENDOTP_API_ERR ยท JWT_VERIFIED ยท GATE_PASS ยท GATE_DISABLED
(kill-switch) ยท LICENSE_OK / LICENSE_UNLICENSED /
LICENSE_LAPSED|SUSPENDED|CANCELLED ยท GATE_FAIL_OPEN (allowed because
Authyo was unreachable and fail-open is set) ยท BACKUPCODE_OK /
BACKUPCODE_FAIL ยท TOTP_BREAKGLASS GATE_PASS /
TOTP_BREAKGLASS GATE_FAIL reason=bad_code|replay / TOTP_NO_ENROLLMENT.
โ ๏ธ You tested in a new terminal before logging out โ right? If not, and
you still have any open SSH session, use it now (open sessions are never
interrupted by the gate).
Fastest first:
sudo authyo-configure --uninstall # remove the PAM hook (restores backup)
sudo systemctl restart ssh # or: systemctl restart sshd
sudo bash authyo-rescue.sh # strips pam_authyo from /etc/pam.d/sshd, backs it up, reloads sshd
sudo bash authyo-rescue.sh --purge # also removes the package + /etc/authyo
sudo cp /etc/pam.d/sshd.authyo-backup-* /etc/pam.d/sshd
sudo systemctl restart ssh
e โ add init=/bin/bash โ boot,/etc/pam.d/sshd.# 1. Remove the PAM hook first (restores your sshd backup)
sudo authyo-configure --uninstall
# 2. Remove the package
sudo apt-get remove authyo-gatekeeper # Debian/Ubuntu
sudo dnf remove authyo-gatekeeper # RHEL/Rocky
# ...or purge config too (Debian): sudo dpkg --purge authyo-gatekeeper
# 3. Optional: remove config + state + logs
sudo rm -rf /etc/authyo /var/lib/authyo-gatekeeper /var/log/authyo
Removing the package also stops and disables the heartbeat timer.
| Symptom | Check |
|---|---|
dpkg: dependency problems | sudo apt-get install -f -y |
rpm: missing dependencies | Install via dnf/yum, not rpm -i (they resolve deps) |
authyo-configure says "must be run as root" | Use sudo |
| Login fails immediately, no OTP sent | /var/log/authyo/gatekeeper.log |
| Test says "wallet balance is low" | Top up the Authyo wallet in the portal |
| Test says "invalid clientId or clientSecret" | Re-check the credentials |
pam_authyo.so not found | Debian: /usr/lib/x86_64-linux-gnu/security/; RHEL: /usr/lib64/security/ |
| "Cannot connect to Authyo" | DNS / firewall / outbound HTTPS to app.authyo.io:443 |
| Login denied even with a good OTP | Server kill-switch? Look for GATE_DISABLED in the log โ re-enable the server in the portal |
| sshd won't start after install | sudo systemctl status ssh + journalctl -xeu ssh; restore /etc/pam.d/sshd.authyo-backup-* |
| Kill-switch / policy not taking effect | systemctl status authyo-gatekeeper-heartbeat.timer; the cache refreshes every 15 min |
app.authyo.io, TLS validated.For the full threat model, see SECURITY.md.
LICENSE.