Two-factor authentication for OpenBSD SSH using Google Authenticator (TOTP), with failed-login logging forwarded to a remote syslog server.
This guide is also available in the following languages:
This repository provides:
| File | Purpose |
|---|---|
setup.sh |
Automated setup script — run once as root |
login_totp |
BSD Auth backend that verifies the TOTP code |
google-authenticator-setup.sh |
Per-user enrolment script |
sshd_config.snippet |
Reference sshd_config additions |
syslog.conf.snippet |
Reference syslog.conf additions for remote forwarding |
SSH client
│
▼
sshd ──── 1. Public-key auth (existing key pair)
│
▼
BSD Auth (login_totp)
│
├── 2. Prompt: "Google Authenticator code: "
├── 3. User enters 6-digit TOTP from the app
├── 4. oathtool verifies the code against ~/.google_authenticator
│
├─ SUCCESS → session opened; auth.info logged locally + forwarded
└─ FAILURE → session closed; auth.warning logged locally + forwarded
- OpenBSD 7.x (tested on 7.4 and 7.5)
- Root or
doasaccess oath-toolkitpackage (pkg_add oath-toolkit) — providesoathtool- A remote syslog server reachable from the host (rsyslog, syslog-ng, etc.)
- Users must have an SSH public key already installed (
~/.ssh/authorized_keys)
doas sh setup.shThe script will:
- Install
oath-toolkitviapkg_add. - Copy
login_totpto/usr/local/libexec/auth/login_totp. - Add a
totplogin class to/etc/login.conf. - Patch
/etc/ssh/sshd_config. - Patch
/etc/syslog.confwith remote forwarding rules. - Restart
syslogdandsshd. - Optionally run
google-authenticator-setup.shto enrol a user.
doas pkg_add oath-toolkitdoas install -o root -g auth -m 550 \
login_totp /usr/local/libexec/auth/login_totpAppend the following to /etc/login.conf:
# TOTP (Google Authenticator) login class
totp:\
:auth=-totp:\
:tc=default:
Then rebuild the login.conf database:
doas cap_mkdb /etc/login.confAdd the lines from sshd_config.snippet to /etc/ssh/sshd_config.
The critical directives are:
AuthenticationMethods publickey,keyboard-interactive:bsdauth
KbdInteractiveAuthentication yes
PasswordAuthentication no
UsePAM no
LogLevel VERBOSE
Verify and restart sshd:
doas sshd -t # verify config
doas rcctl restart sshdAdd the lines from syslog.conf.snippet to /etc/syslog.conf, replacing
REMOTE_SYSLOG_SERVER with your actual server address.
UDP (default):
auth.info @192.168.1.50
*.warning @192.168.1.50
TCP (more reliable):
auth.info @@192.168.1.50
*.warning @@192.168.1.50
For TCP, also enable TCP in /etc/rc.conf.local:
syslogd_flags="-T"
Reload syslogd:
doas rcctl restart syslogdRun the per-user enrolment script (as root or as the user themselves):
doas sh google-authenticator-setup.shThe script:
- Generates a random 160-bit TOTP secret.
- Writes it to
~/.google_authenticator(mode 0600). - Prints a
otpauth://URI and a terminal QR code (ifqrencodeis installed). - Assigns the user to the
totplogin class.
Scan the QR code (or paste the URI) into Google Authenticator, Aegis, Authy, or any TOTP-compatible app.
If you did not use google-authenticator-setup.sh, assign the class manually:
doas usermod -L totp alice# Generate the current TOTP code for a user's secret:
head -1 ~/.google_authenticator | xargs oathtool --totp -bCompare this with the code shown in the authenticator app — they should match.
logger -p auth.info "syslog-test: auth.info forwarding"
logger -p auth.warning "syslog-test: auth.warning forwarding"Check that these messages arrive on the remote syslog server.
Open a new SSH session (keep your existing session open in case something needs fixing):
ssh -v alice@your-serverExpected flow:
- sshd accepts your public key.
- You see the prompt:
Google Authenticator code: - Enter the 6-digit code from the authenticator app.
- Login succeeds or fails; the result appears in
/var/log/authlogand on the remote syslog server.
When login_totp rejects a TOTP code, it emits a message via logger(1):
Apr 27 16:00:01 myhost login_totp[12345]: TOTP reject: failed one-time-password for user 'alice' (service=ssh class=totp)
This message is written to:
- The local syslog (
/var/log/authlogon OpenBSD). - The remote syslog server via the
auth.inforule insyslog.conf.
Additional failed-authentication events are logged by sshd itself:
Apr 27 16:00:01 myhost sshd[12346]: Failed keyboard-interactive for alice from 203.0.113.5 port 54321 ssh2
- Location:
/usr/local/libexec/auth/login_totp - Permissions:
root:auth 0550 - Secret file:
~/.google_authenticator(first line = base-32 TOTP secret) - Logging:
logger -p auth.warningon failure,auth.infoon success - Time tolerance: ±1 × 30-second step (configurable via
TOTP_WINDOW)
A plain-text file; the first line must be the base-32 TOTP secret.
Additional lines (comments) are ignored by login_totp.
Example:
JBSWY3DPEHPK3PXP
# Created by google-authenticator-setup.sh on Mon Apr 27 16:00:00 UTC 2026
Permissions must be 0600, owned by the user.
| Topic | FreeBSD | OpenBSD |
|---|---|---|
| Auth framework | PAM (pam_google_authenticator.so) |
BSD Auth (login_totp script) |
| Login class | n/a | /etc/login.conf totp class |
| Package | security/google-authenticator-pam |
security/oath-toolkit |
| syslog daemon | syslogd / newsyslog |
syslogd (built-in) |
| Remote UDP forward | @host in syslog.conf |
@host in syslog.conf |
| Remote TCP forward | @@host |
@@host (+ syslogd_flags="-T") |
"oathtool not found"
Install oath-toolkit: doas pkg_add oath-toolkit
"No secret file for user"
Run google-authenticator-setup.sh for that user, or manually create
~/.google_authenticator with the base-32 secret on the first line.
TOTP codes always rejected
Ensure the system clock is synchronised (ntpd is enabled on OpenBSD by
default). A clock skew of more than 30 seconds will cause every code to
fail. Increase TOTP_WINDOW in login_totp if needed.
SSH asks for a password instead of a TOTP code
Verify that KbdInteractiveAuthentication yes and
AuthenticationMethods publickey,keyboard-interactive:bsdauth are both
present in /etc/ssh/sshd_config, and that the user is in the totp
login class (doas usermod -L totp <user>).
sshd -t fails after editing sshd_config
Run doas sshd -t and fix any reported errors before restarting sshd.
The backup created by setup.sh is at
/etc/ssh/sshd_config.bak.<timestamp>.
Remote syslog not receiving messages
- Confirm the remote server's UDP/TCP port 514 is reachable:
nc -u -z REMOTE_SYSLOG_SERVER 514 - Check firewall rules on both ends (OpenBSD pf and remote server).
- For TCP forwarding, confirm
syslogd_flags="-T"is in/etc/rc.conf.localandsyslogdhas been restarted.
BSD 2-Clause License. See LICENSE for details.