1. RDP Guard
Authyo Docs
  • ๐Ÿถ Walk through Authyo
  • Error Codes
  • About Pricing
  • APIs
    • Send OTP
      GET
    • Send OTP
      POST
    • Verify OTP
      GET
    • Verify Token
      POST
    • Revoke User Session
      POST
  • Web SDKs
    • JavaScript
    • PHP
    • AngularJS Examples
    • TypeScript Examples
    • React JS
    • Vue JS Examples
  • 2FA SDK
    • JavaScript
    • AngularJS Examples
    • PHP
    • React JS
    • TypeScript Examples
    • Vue JS Examples
  • Native/Mobile SDKs
    • Flutter
  • Integration
    • Google Sheet
    • Firebase
    • Authyo - Make
  • Session Management
    • Introductions
    • JWT Token
  • Notifications
    • Notifications Order
    • Notificatoin Invoice
  • RDP Guard
    • RDP Guard : Windows
    • RDP Guard : Linux (SSH)
    • RDP Guard : Step-Aside on Lapse / Deactivation
    • RDP Guard : Linux - Install & Test Guide
    • Authyo RDP Guard for Windows โ€” What's New (v1.0.63)
    • Authyo RDP Guard for Linux (SSH) โ€” What's New (v1.1.6)
  1. RDP Guard

RDP Guard : Linux (SSH)

Installing Authyo RDP Guard on Linux (SSH)

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.
Run sudo authyo-configure --test and open a second SSH session to
confirm you can still get in. If something is wrong, your existing
session is your safety net.


Before you start

  • A Client ID + Client Secret from an active application at
    https://app.authyo.io.
  • An RDP Guard license key โ€” activation is required during
    authyo-configure before it will write a config.
  • Outbound HTTPS to app.authyo.io:443 from the server.
  • At least one reachable admin contact (email / SMS / WhatsApp / voice)
    and enough Authyo wallet balance to deliver OTPs.
  • Root (use sudo).

Install

Path A โ€” .deb (Ubuntu / Debian) โ€” recommended

Download 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

Path B โ€” .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).

Path C โ€” Build from source

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)

The setup wizard (authyo-configure)

Running sudo authyo-configure walks you through:

  1. Authyo credentials โ€” Client ID, Client Secret, API endpoint
    (https://app.authyo.io).
  2. License activation โ€” paste your key. Activation is required; a
    blank key or a failed activation aborts setup. (An unlicensed install
    still enforces OTP at runtime โ€” the module only logs the license
    status, it never skips the gate.)
  3. Server public IP โ€” auto-detected, for your records.
  4. Admin contacts + routing โ€” see How OTP routing works.
  5. Security policy โ€” trust window, break-glass, fail-closed toggle.

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.


How OTP routing works

The wizard sets up per-user routing by default. This changed from the
original release โ€” read this so contacts behave the way you expect.

  • The first OTP goes to the first matching contact only โ€” never a
    fan-out to every channel at once.
  • routingMode decides which contact:
    • PerUser (what the wizard writes): the OTP goes to the contact
      whose name matches the Linux username logging in (case-insensitive;
      a leading DOMAIN\ is stripped). A contact named admin or
      Administrator is the catch-all fallback if no name matches. Give
      one user several channels by adding multiple contacts with the same
      name.
    • CentralAdmin: every login goes to the full contact list
      regardless of who logs in. This is the fallback only for a hand-edited
      config that omits routingMode.
  • contactPolicy (PrimaryOnly / RoundRobin / AllInOrder) is
    present for backward compatibility but ignored for delivery โ€” do not
    rely on it.
  • Resend: at the OTP prompt, press R to resend (throttled by
    resendCooldownSeconds, default 30s). Each resend auto-advances to the
    next channel
    for the same person (Email โ†’ Sms โ†’ Whatsapp). Only one OTP
    is active at a time โ€” a resend invalidates the previous code.

Break-glass (so you can never lock yourself out)

Four ways to get in when the normal OTP can't be delivered:

MethodWorks offline (Authyo down)?How
Google Authenticator (TOTP)โœ… Yes โ€” verified locally, no networkEnrol during authyo-configure; press G at the prompt
Backup codeโŒ No โ€” needs Authyo reachableGenerate in the Authyo dashboard โ†’ "Backup codes (RDP Guard only)"; press B and type it
Exempt (bypass) userโœ… YesAdd to exemptUsers โ€” that user logs in with credentials only, no OTP
Trusted device windowโœ… Yes if that IP+user logged in recentlySet trust.deviceHours (0โ€“24)
  • The B / G prompts only appear AFTER the paid OTP path fails โ€” this
    protects your OTP spend while keeping recovery available.
  • TOTP is offline (RFC 6238) with a single-use replay guard
    (lastUsedStep) โ€” a code can't be replayed. It's the only path that
    survives Authyo being unreachable.
  • Backup codes are single-use, generated once (default 10),
    XXXX-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).


Multiple servers, one webhook โ€” telling them apart

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).
    It is not secret to you โ€” you own both ends.
  • Subject is PamGuardLog on Linux and RdpGuardLog on Windows, so
    you can tell the two platforms apart too.

The on-disk log is always the source of truth; a slow/down receiver never
delays a login.


The heartbeat service (what runs in the background)

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:

  1. Refreshes the license status and the remote kill-switch flag.
  2. Drains the audit journal (enrollment / revoke events) to the portal.
  3. Optionally refreshes server-authoritative policy (if enabled) โ€” a
    signed policy with anti-rollback versioning.

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.


What got installed where

PathContents
/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-configureInteractive setup CLI
/usr/sbin/authyo-gatekeeper-heartbeatOut-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

Command reference

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

Config reference (/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:

KeyDefaultNotes
authyo.clientId / authyo.clientSecret(required)Secret is plaintext in this 0600 root-owned file
authyo.apiEndpointhttps://app.authyo.io
authyo.timeoutSeconds101โ€“60
contacts[].name / .to / .authway(โ‰ฅ1 required)name is the PerUser match key
policy.routingModePerUser (wizard)CentralAdmin | PerUser
policy.contactPolicyPrimaryOnlyignored for delivery โ€” back-compat only
policy.expiry300OTP validity seconds (min 60)
policy.otpLength64โ€“9
policy.blockOnAuthyoUnreachabletruetrue = fail-closed (deny if Authyo unreachable). false = fail-open.
policy.resendCooldownSeconds300โ€“300; min seconds between resends
policy.backupCodesEnabledtrueoffers [B] at the prompt
policy.exemptUsers[][]usernames that skip OTP (credentials only)
policy.serverAuthoritativefalseenable server-signed policy enforcement
trust.deviceHours00 = always require OTP (most secure); max 24
trust.cachePath/var/lib/authyo-gatekeeper/trust.db
logging.levelinfodebug | info | warn | error
logging.path/var/log/authyo/gatekeeper.log
logging.webhookUrl(empty)set to mirror every log line to your endpoint
logging.webhookTimeoutSeconds51โ€“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

Log markers

/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.


Recovery if locked out

โš ๏ธ 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:

  • Existing SSH session (never interrupted):
    sudo authyo-configure --uninstall     # remove the PAM hook (restores backup)
    sudo systemctl restart ssh            # or: systemctl restart sshd
    
  • Emergency rescue script (ships in the package):
    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
    
  • Cloud serial console (AWS EC2 Serial, Azure Serial, GCP serial):
    sudo cp /etc/pam.d/sshd.authyo-backup-* /etc/pam.d/sshd
    sudo systemctl restart ssh
    
  • Physical / single-user mode: GRUB โ†’ e โ†’ add init=/bin/bash โ†’ boot,
    then restore the backup as above.
  • Disk recovery: mount the disk elsewhere and edit /etc/pam.d/sshd.

Uninstall

# 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.


Troubleshooting

SymptomCheck
dpkg: dependency problemssudo apt-get install -f -y
rpm: missing dependenciesInstall 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 foundDebian: /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 OTPServer kill-switch? Look for GATE_DISABLED in the log โ€” re-enable the server in the portal
sshd won't start after installsudo systemctl status ssh + journalctl -xeu ssh; restore /etc/pam.d/sshd.authyo-backup-*
Kill-switch / policy not taking effectsystemctl status authyo-gatekeeper-heartbeat.timer; the cache refreshes every 15 min

Security notes

  • Config is mode 0600, root-owned. The Client Secret and TOTP secrets
    are plaintext inside it โ€” the file permissions are the protection (same
    model as the SSH host keys next to it).
  • OTP transport: HTTPS to app.authyo.io, TLS validated.
  • JWT verification: every OTP/backup-code verify response carries a JWT
    signed with HMAC-SHA256 using your Client Secret, validated locally โ€”
    a forged "success" without a valid JWT is rejected.
  • TOTP break-glass is offline (RFC 6238) with a single-use replay guard.
  • Server-authoritative policy (optional) is HMAC-SHA256-signed with
    anti-rollback versioning, so a stale or tampered local config can't grant
    a bypass.
  • No bypass of the OS: the module layers OTP on top of the normal PAM
    stack; it never substitutes for the underlying credential check.

For the full threat model, see SECURITY.md.


Support

  • Documentation: https://authyo.io/gatekeeper
  • Email: support@authyo.io
  • License: Apache-2.0 (the PAM module). See LICENSE.
  • Copyright 2026 Konceptwise Digital Media Pvt. Ltd.
Modified atย 2026-07-02 06:11:36
Previous
RDP Guard : Windows
Next
RDP Guard : Step-Aside on Lapse / Deactivation
Built with