multiOTP 5.10.2.2
Overview
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 guide covers the multiOTP 5.10.2.2 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud identity, secrets and PKI listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The image gives you multiOTP's web administration console and its command-line tool on one instance, hardened and ready to use.
What you get:
- 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, licenses and the file list are on the image in
/usr/share/doc/sac-multiotp/. - HTTPS from the first boot. nginx is the only public listener (ports 80 and 443). It serves a certificate generated on your instance (an end-entity certificate, not a certificate authority), and 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 requires HTTP Basic auth (user
admin), followed by the console's own login (useradmin), both with the same password generated on your instance. Upstream's defaultadmin/1234never works, not even briefly: 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, and multiOTP's SOAP/WSDL interface is refused. PHP runs multiOTP as its own unprivileged user with access limited to its own directories.
- Protection 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, and 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.
- 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.
- Defaults: 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. Also allow 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.
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 select your Region, then choose Launch through EC2. Choose m5.large, your key pair and a security group as described above.
Or launch with the AWS CLI, replacing 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/sac/multiotp-domain.preset with user data (for example, a cloud-init write_files entry). Otherwise, first boot uses the instance's public DNS name.
Connect to your instance
Connect over SSH. The SSH user for Ubuntu 24.04 is ubuntu:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
systemctl is-active multiotp-firstboot php8.3-fpm nginxactive active activeOnly 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 -utcp 0.0.0.0:22 tcp 0.0.0.0:443 tcp 0.0.0.0:80 tcp [::]:22 tcp [::]:443 tcp [::]:80Your address and password
sudo grep -v PASSWORD /root/multiotp-credentials.txtFirst boot wrote this file. It is root-only, mode 0600. 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://secureanycloud.com/stacks/multiotp-5-10-2-2/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-saltThe 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).
Run the self-test
sudo multiotp-selftestThe self-test runs over HTTPS, pinned to the instance's own certificate. First, 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 that it 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. Finally, it 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_OKSign in to the console
Open MULTIOTP_URL in your browser, from a network that 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 choose whether the user must type a prefix PIN before the code. Then select Add this user. The user appears in the list. Select Print to show the QR code to scan into the user's authenticator app:
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 11Create a QR code image to hand the token to the user. 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-2exit code 16 /var/lib/multiotp/tmp/alice-qr.png: PNG image data, 228 x 228Copy the image to your computer (for example, ssh ubuntu@<public-ip> sudo cat /var/lib/multiotp/tmp/alice-qr.png > alice-qr.png) and give it to the user. Then remove it on the instance, because the QR code carries the token's secret:
sudo shred -u /var/lib/multiotp/tmp/alice-qr.png && echo removedremovedNever put a token secret or a password on the multiotp command line, because sudo logs its arguments. Let multiOTP generate the secret, as above, or import token files.
Check codes, replay protection and lockout
The user types the 6-digit code from their app. In this example, 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 then a wrong code is tried:
CODE=$(sudo multiotp -urllink alice | python3 /usr/local/lib/sac/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 99Exit 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 then 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/sac/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 0sudo multiotp -userslist lists the users, sudo multiotp -delete alice removes one, and sudo multiotp -help documents every command.
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/sac/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 403With the admin credentials, only / (the console) and /md5.js answer 200. The command-line program, the configuration, users, tokens and keys stay 404, and ?wsdl / ?soap stay 403. Port 80 only redirects:
D=$(head -1 /etc/sac/multiotp-domain); curl -s -o /dev/null -w "%{http_code}\n" --resolve "$D:80:127.0.0.1" "http://$D/"301Change the admin password
sudo multiotp-set-admin-passwordThe command 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 password (at least 16 printable characters, no :), pass it on standard input, never as an argument. Run sudo multiotp-set-admin-password --stdin, type the password, 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.
Use your own domain and certificate
Point a DNS name at the instance, then run:
sudo multiotp-set-domain mfa.example.comnginx 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 limit console access to specific networks, even inside your security group, edit /etc/nginx/sac/multiotp-access.conf (allow lines, then deny all;) and run sudo nginx -t && sudo systemctl reload nginx.
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.tgzThe 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.
Check the security posture
View multiOTP's own settings as the service user sees them (no secret values are shown):
sudo setpriv --reuid=multiotp --regid=multiotp --init-groups --reset-env php /usr/local/lib/sac/multiotp/multiotp-cfg.php show </dev/nullencryption_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=600Metadata 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.2543Confirm that the certificate is an end-entity certificate for your instance's name:
openssl x509 -in /etc/ssl/multiotp/server.crt -noout -ext basicConstraints,extendedKeyUsageX509v3 Basic Constraints: critical CA:FALSE X509v3 Extended Key Usage: TLS Web Server AuthenticationNotes
- 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; SecureAnyCloud 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/sac-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; SecureAnyCloud 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
SecureAnyCloud provides 24/7 technical support for this image by email and live chat: sales@secureanycloud.com.
Deploy on AWS
Launch multiOTP 5.10.2.2 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com