privacyIDEA on Ubuntu 24.04 on Azure User Guide
Overview
privacyIDEA is an open source multi factor authentication server. It manages second factors for your users, such as TOTP and HOTP one time password tokens, WebAuthn and FIDO2 security keys, SMS and email challenges, and paper tokens, and it answers authentication requests over a documented REST API. Because it is a standalone authentication back end rather than an identity provider, it slots in behind things you already run: RADIUS clients such as VPN concentrators, LDAP proxies, SSH, Keycloak and SAML providers, and web applications you write yourself. Your user accounts stay where they already live, in Active Directory, LDAP or a SQL database, and privacyIDEA adds the second factor on top.
The cloudimg image installs privacyIDEA 3.13.3 into a dedicated Python virtual environment, backs it with a local MariaDB database, serves it with gunicorn bound to loopback and fronts it with nginx, then locks the result down for marketplace use. privacyIDEA holds several distinct secrets, and this image generates every one of them uniquely on the first boot of each VM: the encryption key that protects token seeds in the database, the RSA keypair that signs the tamper evident audit log, the Flask session key, the password hashing pepper, the database password, and the TLS certificate. None of them is baked into the image. privacyIDEA ships no default administrator account of its own, so the only administrator that exists is the one created on your first boot, with a randomly generated password written to a root only file. Backed by 24/7 cloudimg support.
What is included:
- privacyIDEA 3.13.3 installed in a dedicated Python virtual environment and run by gunicorn as the
privacyideasystemd service - The full privacyIDEA management interface and REST API on
:80, and on:443with a per VM self signed certificate - MariaDB 10.11 bound to loopback, holding the token, configuration and audit tables
- A per VM encryption key protecting token seeds, generated on first boot and never shared between VMs
- A per VM RSA keypair signing the tamper evident audit log
- A per VM Flask session key, password pepper and database password, none of them baked into the image
- An administrator account created on first boot with a unique, randomly generated password
- privacyIDEA's own anonymous
/healthz/readiness endpoint for Azure Load Balancer probes, reporting the encryption key state rather than merely that a socket is open privacyidea.service,nginx.serviceandmariadb.serviceas systemd units, enabled and active- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point. NSG inbound: allow 22/tcp from your management network, 443/tcp for the management interface and API, and 80/tcp if you want plain HTTP as well. Because privacyIDEA is an authentication server, treat port exposure conservatively and restrict access to the networks that need to reach it.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for privacyIDEA by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTPS (443). Then Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name privacyidea \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_rsa.pub \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name privacyidea --port 443 --priority 1001
az vm open-port --resource-group <your-rg> --name privacyidea --port 80 --priority 1002
Step 3 - Confirm the services are running
SSH to the VM as azureuser and confirm privacyIDEA, nginx and MariaDB are up. The /healthz/ endpoint is privacyIDEA's own readiness probe: it reports "hsm": "OK" only once the encryption key has been loaded, so a 200 here means the server is genuinely ready to issue and validate tokens, not just that a web server is listening.
systemctl is-active privacyidea nginx mariadb
curl -s http://127.0.0.1/healthz/

The first boot takes a few extra seconds compared with later boots, because that is when the per VM secrets are generated and the database schema is created. If /healthz/ is not yet answering, wait a moment and try again.
Step 4 - Retrieve the per VM administrator password
Every VM generates its own administrator password on first boot and writes it to a root only file. Nothing is shared between deployments and there is no default password to change.
sudo cat /root/privacyidea-credentials.txt

The file records the URL privacyIDEA resolved for itself. Azure's instance metadata service returns an empty value for a Standard SKU public IP, so a VM can only discover its own private address. If you are connecting from outside the VNet, browse to the VM's public IP address or DNS name, which you will find on the VM's Overview blade in the Azure Portal.
Step 5 - Sign in to the management interface
Open https://<your-vm-public-ip>/ in a browser. The image ships a self signed certificate generated for this VM, so your browser will warn on first visit until you install a certificate of your own (see Step 9). Sign in with the username and password from Step 4.

On your very first sign in privacyIDEA shows a short welcome tour introducing the product. Click through it, or dismiss it, to reach the management interface.
Step 6 - Enroll your first token
Issuing second factors is what privacyIDEA is for, so this is the step worth doing first. Go to Tokens -> Enroll Token, choose TOTP as the token type and select Enroll Token. privacyIDEA generates a token seed, stores it encrypted with this VM's own encryption key, and displays a QR code.

Scan the QR code with any standard authenticator app to add the token. Because the QR code carries the token's secret, treat it the way you would treat a password: enroll where nobody can see the screen, and use Regenerate QR Code if you think it was observed.
Newly enrolled tokens appear in Tokens -> All Tokens with their serial, type and status.

You can do the same from the command line:
export PRIVACYIDEA_CONFIGFILE=/etc/privacyidea/pi.cfg
sudo -E /opt/privacyidea/venv/bin/pi-tokenjanitor find

Step 7 - Authenticate against the REST API
Applications talk to privacyIDEA over its REST API. Authenticating to POST /auth returns a signed JSON Web Token which is then presented on subsequent calls. This is the same flow your RADIUS front end, LDAP proxy or application will use.
PI_PW='<PRIVACYIDEA_PASSWORD>'
curl -s -X POST http://127.0.0.1/auth -d "username=admin" -d "password=${PI_PW}" \
| python3 -c 'import json,sys; v=json.load(sys.stdin)["result"]["value"]; print("role:", v["role"]); print("token issued:", bool(v["token"]))'
A wrong password is refused, and the returned token is accepted on protected endpoints:
PI_PW='<PRIVACYIDEA_PASSWORD>'
curl -s -X POST http://127.0.0.1/auth -d "username=admin" -d "password=definitely-wrong" \
| python3 -c 'import json,sys; print("wrong password rejected:", json.load(sys.stdin)["result"]["status"] is False)'
TOKEN=$(curl -s -X POST http://127.0.0.1/auth -d "username=admin" -d "password=${PI_PW}" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["result"]["value"]["token"])')
curl -s -H "Authorization: ${TOKEN}" http://127.0.0.1/token/ \
| python3 -c 'import json,sys; print("tokens visible:", json.load(sys.stdin)["result"]["value"]["count"])'

Step 8 - Connect your user source
The image deliberately ships with no user source configured, because privacyIDEA is designed to sit in front of the directory you already run. Until you add one, you can enroll tokens that are not assigned to a user, which is enough to explore the product but not to authenticate anybody.
To connect a directory, sign in to the management interface and go to Config -> Users to create a resolver, which is a connection to Active Directory, an LDAP server, a SQL database or a flat file. Then go to Config -> Realms and create a realm that contains that resolver. A realm is the namespace your users authenticate against; once one exists you can assign tokens to real users and point your RADIUS or application front end at privacyIDEA.
Every administrative action is written to the signed audit log, under Audit, which records who did what and when.

Step 9 - Install a production TLS certificate
privacyIDEA carries authentication traffic, so it should be reached over HTTPS with a certificate your clients trust. The image generates a self signed certificate for the VM on first boot so that port 443 works immediately; replace it with a real one before production use.
Copy your certificate and private key over the generated pair and reload nginx:
sudo install -o root -g privacyidea -m 0644 fullchain.pem /etc/privacyidea/tls/server.crt
sudo install -o root -g privacyidea -m 0640 privkey.pem /etc/privacyidea/tls/server.key
sudo nginx -t && sudo systemctl reload nginx
Once you have a real certificate and a DNS name, set PI_BASE_URL in /etc/privacyidea/pi.cfg to that name. privacyIDEA never derives links from the incoming Host header, so password recovery and notification links use this value.
With TLS and a proper hostname in place you can also re enable passkey and WebAuthn sign in. The image hides the passkey button by default because WebAuthn requires a browser secure context and a configured relying party, neither of which holds when the appliance is reached over plain HTTP on an IP address. Remove the policy to bring it back, then set a webauthn_relying_party_id policy for your domain:
export PRIVACYIDEA_CONFIGFILE=/etc/privacyidea/pi.cfg
sudo -E /opt/privacyidea/venv/bin/pi-manage config policy delete cloudimg_hide_passkey
Maintenance
Service management. privacyIDEA runs under gunicorn as privacyidea.service, behind nginx.service, with mariadb.service holding the data:
systemctl status privacyidea --no-pager --lines=0
Restart with sudo systemctl restart privacyidea, and check sudo journalctl -u privacyidea -n 50 if it does not come back. Application logs are at /var/log/privacyidea/privacyidea.log.
Where things live. The virtual environment is at /opt/privacyidea/venv, the WSGI entry point at /opt/privacyidea/privacyideaapp.py, and all configuration and secret material under /etc/privacyidea:
ls -l /etc/privacyidea/
pi.cfg holds the configuration, enckey is the encryption key protecting token seeds, and private.pem / public.pem sign the audit log. Keep them backed up: without enckey the token seeds in the database cannot be decrypted and every enrolled token is lost.
Backups. privacyIDEA ships its own backup command, which captures both the database and the configuration:
sudo -E PRIVACYIDEA_CONFIGFILE=/etc/privacyidea/pi.cfg /opt/privacyidea/venv/bin/pi-manage backup create --help
Store backups somewhere separate from the VM, and remember they contain the encryption key.
Operating system updates. The image ships fully patched and unattended security upgrades remain enabled, so security updates continue to be applied automatically. Apply the rest on your own schedule:
sudo apt-get update && sudo apt-get -s upgrade | tail -5
Upgrading privacyIDEA. Upgrade inside the virtual environment, then run any schema migrations and restart:
sudo /opt/privacyidea/venv/bin/pip install --upgrade privacyidea
sudo -E PRIVACYIDEA_CONFIGFILE=/etc/privacyidea/pi.cfg /opt/privacyidea/venv/bin/pi-manage db upgrade
sudo systemctl restart privacyidea
Read the upstream release notes before a major upgrade, and take a backup first.
Troubleshooting
The web interface does not load. Check that nginx and privacyIDEA are both active, and that your NSG allows inbound 443/tcp (or 80/tcp) from your client address. curl -s http://127.0.0.1/healthz/ on the VM itself distinguishes a network problem from an application problem.
/healthz/ returns 503 with "hsm": "fail". privacyIDEA cannot read its encryption key. Confirm /etc/privacyidea/enckey exists and is readable by the privacyidea user; the file should be mode 0640 and group owned by privacyidea.
Sign in is rejected. Confirm you are using the password from /root/privacyidea-credentials.txt on this VM; passwords are not shared between deployments. If you have lost it, set a new one with pi-manage admin change admin --password <new> (with PRIVACYIDEA_CONFIGFILE exported).
The browser warns the certificate is not trusted. Expected until you install your own certificate; the image generates a self signed one per VM. See Step 9.
The passkey button is missing from the sign in page. Hidden by design on the default HTTP deployment, where WebAuthn cannot work. See Step 9 to re enable it once you have TLS and a domain.
Support
This image is published by cloudimg with 24/7 support. For product documentation see the privacyIDEA documentation. For issues with the image itself, contact cloudimg support through the Azure Marketplace listing.
privacyIDEA is licensed under the GNU Affero General Public License v3.0. This is a repackaged open source software product with additional charges for cloudimg support services. privacyIDEA is a trademark of its respective owners. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.