multiOTP 5.10.2.2 on AWS User Guide
Overview
This guide covers launching and using the multiOTP 5.10.2.2 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg identity, secrets and PKI listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
multiOTP is an open-source strong-authentication server. It manages users and their one-time-password tokens (time-based TOTP, counter-based HOTP and Mobile-OTP, provisioned with QR codes for apps such as Google Authenticator, Microsoft Authenticator or FreeOTP) and checks the codes they type. This image gives you multiOTP's web administration console and its command-line tool on one instance, ready to use.
The cloudimg image is a hardened, ready-to-use multiOTP server:
- The open-source edition only. multiOTP 5.10.2.2 from the upstream release tarball (LGPL-3.0-or-later), checked against its checksum and installed file for file. The commercial multiOTP Pro / Enterprise editions, the multiOTP virtual appliance, the Windows installers and the sample token files are not included. Source, licences and the file list are on the image in
/usr/share/doc/cloudimg-multiotp/. - HTTPS from the first boot. nginx is the only public listener (ports 80 and 443) and serves a certificate generated on your instance (an end-entity certificate, not a certificate authority); port 80 redirects to HTTPS. The address is set from the instance's EC2 public DNS name at first boot and can be changed to your own domain.
- Two locks on the console. The whole site needs HTTP Basic auth (user
admin), and then the console's own login (useradmin), both with the same password generated on your instance. Upstream's defaultadmin/1234never works, not even for a minute: the password is set before the web service is allowed to start. - Every secret is created on your instance. First boot generates the admin password, multiOTP's encryption key (token seeds, the admin password hash and the client/server secret are stored encrypted with it), multiOTP's hash salt (upstream's built-in salt is never used) and the client/server shared secret. Nothing is shared between instances, and there are no demo users or tokens.
- No phoning home. multiOTP's anonymous weekly usage statistics (sent to stats.multiotp.net) are switched off.
- Only the console is served. Over HTTPS you reach the console page and its one script, nothing else: the configuration, users, tokens, keys, logs and the command-line program are outside the web root or return 404; multiOTP's SOAP/WSDL interface is refused. PHP runs multiOTP as its own unprivileged user with access limited to its own directories.
- Protected against cross-site use. The console's state-changing calls are refused when they come from another site, its session cookie is
Secure,HttpOnlyandSameSite=Strict, and upstream's permissive cross-origin (CORS) headers are removed. - Real client addresses and rate limits. nginx hands multiOTP the real TCP peer, never a client's own
X-Forwarded-For; every client address is rate limited, admin logins more strictly. - Metadata access blocked. Every service, and the multiOTP command-line tool, is blocked from the instance metadata service, so multiOTP's outbound features (SMS gateways, client/server mode, AD/LDAP sync) can never reach it. The AMI requires IMDSv2.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: multiOTP 5.10.2.2 (open-source edition), nginx, PHP 8.3 (FPM), Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. No RADIUS server is installed: this image is the multiOTP console and command-line tool.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 22 (SSH) and TCP 443 (the console) from your own administration networks only, and TCP 80 (redirects to HTTPS) if you want plain
http://links to work. Nothing else is needed: no other port listens. - For production, a DNS name you control (for example
mfa.example.com) and a certificate from a certificate authority for it.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick multiOTP 5.10.2.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --image-id <ami-id-from-the-listing> --instance-type m5.large \
--key-name <your-key-pair> --security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=multiotp}]'
First boot takes a few seconds after cloud-init has run. To use your own name from the start, put it in /etc/cloudimg/multiotp-domain.preset with user data (for example a cloud-init write_files entry); otherwise first boot uses the instance's public DNS name.
Step 2: Connect to your instance
| Operating system | SSH user |
|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
systemctl is-active multiotp-firstboot php8.3-fpm nginx
active
active
active
Only SSH, HTTP and HTTPS listen; there is no RADIUS (UDP 1812/1813) or any other listener:
sudo ss -tlnu | awk 'NR>1{print $1, $5}' | grep -vE '127\.0\.0\.5[34]|:68$|:323$' | sort -u
tcp 0.0.0.0:22
tcp 0.0.0.0:443
tcp 0.0.0.0:80
tcp [::]:22
tcp [::]:443
tcp [::]:80
Step 4: Your address and password
sudo grep -v PASSWORD /root/multiotp-credentials.txt
The file (root only, mode 0600) was written by first boot. Without its password line it reads:
# multiOTP 5.10.2.2 - written on first boot by multiotp-firstboot.service. Unique to this instance.
MULTIOTP_URL=https://ec2-100-24-2-52.compute-1.amazonaws.com/
MULTIOTP_ADMIN_USER=admin
TLS_SHA256=A4:58:A7:5F:C4:5E:4A:C7:55:E1:09:78:9F:16:3F:57:55:13:3C:39:EF:25:4B:0C:6F:2D:F6:CA:B3:E5:51:4E
TLS_SPKI_SHA256=(the base64 SHA-256 of the certificate's public key)
#
# The console at MULTIOTP_URL asks for this login twice: the browser's Basic-auth prompt, then the console's own form
# (user admin). Change it with: sudo multiotp-set-admin-password (random) or --stdin (your own, >= 16 characters).
# Command line (as the multiotp user): sudo multiotp -help e.g. sudo multiotp -fastcreatenopin alice
# multiOTP's per-instance encryption key and hash salt: /etc/multiotp/keys (back them up with /etc/multiotp).
# The HTTPS certificate is self-signed and unique to this instance: compare its SHA-256 fingerprint (TLS_SHA256), or
# replace it with a CA-issued certificate for your own name. Your own name: sudo multiotp-set-domain mfa.example.com
# User guide: https://www.cloudimg.co.uk/guides/multiotp-5-10-2-2-on-ubuntu-24-04-aws/
The admin password is the MULTIOTP_ADMIN_PASSWORD line, unique to your instance; read it when you need it with sudo grep '^MULTIOTP_ADMIN_PASSWORD=' /root/multiotp-credentials.txt.
multiOTP keeps its data in /etc/multiotp: the configuration, users, tokens and groups (each readable by the multiotp service user only) and the two per-instance key files, which root owns and the service can only read:
sudo ls -l /etc/multiotp/ /etc/multiotp/keys | awk '$1 ~ /^[d-]/{print $1, $3, $4, $NF}'
drwx------ multiotp multiotp config
drwx------ multiotp multiotp credusers
drwx------ multiotp multiotp ddns
drwx------ multiotp multiotp devices
drwx------ multiotp multiotp groups
drwxr-x--- root multiotp keys
drwx------ multiotp multiotp tokens
drwx------ multiotp multiotp users
-r--r----- root multiotp encryption.key
-r--r----- root multiotp hash-salt
The certificate is self-signed and unique to your instance. Do not add it to a trust store: compare the fingerprint your browser shows with TLS_SHA256, or replace the certificate with one from a certificate authority (Step 11).
Step 5: Run the self-test
sudo multiotp-selftest
Over HTTPS (pinned to the instance's own certificate) it checks that the console refuses no credentials, upstream's default admin / 1234 and a wrong password, both at the Basic-auth prompt and at the console's own login, and accepts your password; then it creates a throwaway TOTP user on the command line and checks that a valid code is accepted, the same code a second time is refused and a wrong code is refused, and removes the user again:
round trip OK: console refuses no credentials, admin/1234 and a wrong password (Basic auth and console login), accepts the per-instance password; TOTP valid 0, replay 26, wrong 99; test user removed
MULTIOTP_SELFTEST_OK
Step 6: Sign in to the console
Open MULTIOTP_URL in your browser (from a network your security group allows), check the certificate fingerprint against TLS_SHA256 and accept it. Your browser first asks for a user name and password: enter admin and your admin password. The multiOTP web administration console opens, not yet signed in:

Sign in to the console with admin and the same password. A wrong password (or upstream's old default 1234) leaves you at User NOT authenticated:

With your password the console shows User authenticated, the administration sections and the list of users:

Under Add a new user enter a user name, choose the token type (TOTP for an authenticator app) and whether the user must type a prefix PIN before the code, then Add this user. The user appears in the list; Print shows the QR code to scan into the user's authenticator app:

Step 7: Create a user and token on the command line
sudo multiotp runs the multiOTP command-line tool as the multiotp service user (so the console sees the same users), with metadata access blocked. Create a TOTP user without a prefix PIN (exit code 11 means "user successfully created"):
sudo multiotp -fastcreatenopin alice; echo "exit code $?"
exit code 11
Hand the token to the user as a QR code image (exit code 16 means "QR code created"). The tool runs as the multiotp user, so write the image into its private directory /var/lib/multiotp/tmp:
sudo multiotp -qrcode alice /var/lib/multiotp/tmp/alice-qr.png; echo "exit code $?"; sudo file /var/lib/multiotp/tmp/alice-qr.png | cut -d, -f1-2
exit code 16
/var/lib/multiotp/tmp/alice-qr.png: PNG image data, 228 x 228
Copy it to your computer (for example ssh ubuntu@<public-ip> sudo cat /var/lib/multiotp/tmp/alice-qr.png > alice-qr.png), give it to the user, then remove it on the instance, since the QR code carries the token's secret:
sudo shred -u /var/lib/multiotp/tmp/alice-qr.png && echo removed
removed
Never put a token secret or a password on the multiotp command line (sudo logs its arguments); let multiOTP generate the secret, as above, or import token files.
Step 8: Check codes, replay protection and lockout
The user types the 6-digit code from their app. Here a code is calculated on the instance (totp.py reads the token link on standard input and prints only the current code), checked once, checked again, and a wrong code is tried:
CODE=$(sudo multiotp -urllink alice | python3 /usr/local/lib/cloudimg/multiotp/totp.py)
sudo multiotp alice "$CODE"; echo "first use: exit code $?"
sudo multiotp alice "$CODE"; echo "same code again: exit code $?"
sudo multiotp alice 000000; echo "wrong code: exit code $?"
first use: exit code 0
Reply-Message := "ERROR: This token has already been used"
same code again: exit code 26
Reply-Message := "ERROR: Authentication failed (and other possible unknown errors)"
wrong code: exit code 99
Exit code 0 is a successful authentication. A code can be used once only (26), so a code that someone overhears is useless afterwards. After 3 failed codes multiOTP delays the user for 300 seconds (25), and during the delay even a valid code is refused; after 6 failures the user is locked (24) until an administrator unlocks them. Two more wrong codes (three in all) put alice into the delay, and the administrator unlocks her:
for c in 111111 222222; do sudo multiotp alice $c >/dev/null; echo -n "$? "; done; echo
CODE=$(sudo multiotp -urllink alice | python3 /usr/local/lib/cloudimg/multiotp/totp.py 1)
sudo multiotp alice "$CODE" >/dev/null; echo "valid code while delayed: exit code $?"
sudo multiotp -unlock alice; echo "unlock: exit code $?"
sudo multiotp alice "$CODE"; echo "valid code after unlock: exit code $?"
99 99
valid code while delayed: exit code 25
unlock: exit code 11
valid code after unlock: exit code 0
sudo multiotp -userslist lists the users, sudo multiotp -delete alice removes one, and sudo multiotp -help documents every command.
Step 9: What is served over HTTPS
Without credentials every path answers 401 (Basic auth), except the SOAP/WSDL interface, which is refused outright (403):
D=$(head -1 /etc/cloudimg/multiotp-domain); for p in / /md5.js /multiotp.php /config/multiotp.ini /users/alice.db "/?wsdl"; do printf "%-22s %s\n" "$p" "$(curl -s -o /dev/null -w "%{http_code}" --cacert /etc/ssl/multiotp/server.crt --resolve "$D:443:127.0.0.1" "https://$D$p")"; done
/ 401
/md5.js 401
/multiotp.php 404
/config/multiotp.ini 404
/users/alice.db 404
/?wsdl 403
With the admin credentials only / (the console) and /md5.js answer 200: the command-line program, the configuration, users, tokens and keys stay 404, ?wsdl / ?soap stay 403. Port 80 only redirects:
D=$(head -1 /etc/cloudimg/multiotp-domain); curl -s -o /dev/null -w "%{http_code}\n" --resolve "$D:80:127.0.0.1" "http://$D/"
301
Step 10: Change the admin password
sudo multiotp-set-admin-password
It generates a new random 24-character password, sets it for the console login and the Basic-auth prompt together, and writes it to /root/multiotp-credentials.txt. To choose your own (at least 16 printable characters, no :), pass it on standard input, never as an argument: sudo multiotp-set-admin-password --stdin and type it, then press Enter. Do not use the console's own Change admin password section: it changes only the console login, not the Basic-auth prompt in front of it.
Step 11: Use your own domain and certificate
Point a DNS name at the instance, then:
sudo multiotp-set-domain mfa.example.com
nginx then answers only to that name (and to the instance's own addresses), and a new self-signed certificate for it is generated. To use a certificate from a certificate authority instead, replace /etc/ssl/multiotp/server.crt (full chain) and /etc/ssl/multiotp/server.key (root only, mode 0600) and run sudo systemctl reload nginx. Running multiotp-set-domain again generates a self-signed certificate again.
To let only some networks reach the console even inside your security group, edit /etc/nginx/cloudimg/multiotp-access.conf (allow lines, then deny all;) and run sudo nginx -t && sudo systemctl reload nginx.
Step 12: Back up multiOTP
Everything multiOTP knows is in /etc/multiotp: the configuration, users, tokens, groups and the two key files. Back it up together; without /etc/multiotp/keys the stored token secrets cannot be read:
sudo sh -c 'umask 077; tar -C / -czf /root/multiotp-backup.tgz etc/multiotp' && sudo ls -l /root/multiotp-backup.tgz | awk '{print $1, $3, $NF}'
-rw------- root /root/multiotp-backup.tgz
The archive contains every token secret: copy it to protected storage, then delete it from the instance (sudo shred -u /root/multiotp-backup.tgz). To restore on a new instance from this image, stop nginx and php8.3-fpm, unpack the archive over / as root, then start them again.
Step 13: Check the security posture
multiOTP's own settings, as the service user sees them (no secret values):
sudo setpriv --reuid=multiotp --regid=multiotp --init-groups --reset-env php /usr/local/lib/cloudimg/multiotp/multiotp-cfg.php show </dev/null
encryption_key_full_path=/etc/multiotp/keys/encryption.key
hash_salt_full_path=/etc/multiotp/keys/hash-salt
anonymous_stat=0
encryption_key_default=no
hash_salt_default=no
server_secret_default=no
admin_password_1234=no
admin_password_set=yes
syslog_server=
server_url=
debug=0
max_block_failures=6
max_delayed_failures=3
failure_delayed_time=300
max_time_window=600
Metadata access is denied to PHP-FPM, nginx and the first-boot service (the multiotp command-line wrapper uses the same list):
sudo systemctl show -p IPAddressDeny php8.3-fpm nginx multiotp-firstboot | grep -c 169.254.169.254
3
The certificate is an end-entity certificate for your instance's name:
openssl x509 -in /etc/ssl/multiotp/server.crt -noout -ext basicConstraints,extendedKeyUsage
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Server Authentication
Notes
- No RADIUS server. multiOTP is often used behind FreeRADIUS for VPN logins; this image does not install it, so nothing listens on UDP 1812/1813. The console and the
multiotpcommand-line tool (exit code 0 for a valid code) are what it provides. - Client/server mode. multiOTP's XML client/server interface is behind the same Basic auth and uses the shared secret generated on your instance; it is not configured for any client by default.
- Console scripts. The upstream console is a single page that relies on inline scripts and
eval()of its own answers, so its Content-Security-Policy allows'unsafe-inline'and'unsafe-eval'for scripts from the console itself; nothing loads from another origin. The console page callsexec()for its status line, so PHP keepsexecandshell_execenabled for themultiotpuser (other process functions are disabled). - Token secrets at rest. multiOTP stores token secrets in its own reversible encoding keyed by the per-instance encryption key; the real protection is that only the
multiotpuser and root can read/etc/multiotp. - Known upstream issues. multiOTP issue #261: a TOTP code can be replayed if a user's stored time offset (
delta_time) is not a multiple of the token's interval. multiOTP itself only writes multiples, so it needs an administrator to change an existing token's interval or import a token carrying such an offset; avoid both. The bundled phpseclib 1.0 and nusoap 0.9.5 libraries carry published advisories in X.509/ASN.1 parsing, SSH and WSDL display that multiOTP's code paths do not use (WSDL is refused, see Step 9); the AES decryption used when an administrator imports a token file is reachable only by the signed-in administrator. - Updates. Ubuntu security updates install automatically. multiOTP itself is installed from its release tarball; cloudimg publishes a new version of this image for new multiOTP releases.
Licensing and trademarks
multiOTP is free software under the GNU Lesser General Public License version 3 or later (LGPL-3.0-or-later); the licence texts and the source details are in /usr/share/doc/cloudimg-multiotp/. Bundled libraries keep their own licences (MIT, LGPL, BSD), named in each file. multiOTP is a name of SysCo systemes de communication sa, used here only to identify the open-source software this image packages; cloudimg is not affiliated with, endorsed by, or sponsored by SysCo systemes de communication sa. All other product names are trademarks of their respective owners.
Support
cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk.