Wizarr 2026.10.1 on AWS User Guide
Overview
This guide covers launching and verifying Wizarr 2026.10.1 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other media and streaming products in the listing are described on the listing's guide hub.
Wizarr is a free, self-hosted invitation and onboarding manager for media servers (MIT licence). You connect it to your Plex, Jellyfin, Emby, Audiobookshelf, Komga, Kavita, Navidrome or RomM server, create invitation links that can expire, be limited to a number of uses and grant chosen libraries, and send them to the people you want to invite. Each guest opens the link, creates an account on your media server through Wizarr's step-by-step onboarding pages, and is told which apps to install. Wizarr keeps the list of invited users, so you can see who joined with which invitation and remove access later.
What is on the image:
- Wizarr 2026.10.1, built from the unmodified upstream release (the tag archive is checked against a pinned SHA-256 and its tag commit), under
/opt/wizarr, owned by root and read-only to the service - CPython 3.13.18 (python-build-standalone) for Wizarr, because Wizarr needs Python 3.13 and Ubuntu 24.04 ships 3.12. The Python packages come from Wizarr's own lock file, with five deliberately newer releases that fix published advisories; see Security posture
- Wizarr (gunicorn) on
127.0.0.1:5690only, behind nginx serving HTTPS on port 443 with a certificate created on your instance; port 80 redirects to HTTPS - The administrator account created on first boot, before Wizarr or nginx first start, with a password generated on your instance, so Wizarr's setup wizard is never reachable
- An egress fence that stops Wizarr from reaching instance metadata, private and reserved address ranges and your VPC, with a helper to allow a media server on a private address
wizarr-set-hostname(serve Wizarr under your own DNS name),wizarr-egress-allow(allow a private media server),wizarr-rotate-credentials(new password and session key) andwizarr-selftest(an end-to-end check)
Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3
Security posture
- No setup wizard, no default login. Upstream Wizarr lets the first visitor to its
/setup/page create the administrator. On this image, first boot creates the single administratoradminthrough Wizarr's own models, with a password generated on your instance and handed over on standard input (never a command line), before Wizarr or nginx start./setup/only redirects, a second administrator cannot be created through it, and anonymous requests to the admin pages and the API are refused. The password is written to a root-only file; Wizarr keeps only a scrypt hash of it. - Per-instance secrets. The Flask
SECRET_KEYthat signs sessions, the administrator password and the TLS key are generated on your instance. The image ships without a database: first boot creates it, so no two instances share a secret or a session. - Fails closed. Wizarr and nginx start only after first boot has finished. A start guard runs before every Wizarr start and refuses to start it without the administrator, with an empty password hash, without the session key, with Wizarr's
DISABLE_BUILTIN_AUTHsign-in bypass set, with gunicorn moved off loopback, with a/opt/wizarr/.envfile, or without the egress fence. - Only nginx faces the network. Exactly ports 22, 80 and 443 listen beyond loopback. Wizarr listens on
127.0.0.1:5690, and a firewall rule lets only root, nginx and thewizarraccount open connections to it. Wizarr runs as the unprivilegedwizarruser under a systemd sandbox (read-only system, no home directories, no new privileges, its own/tmp); its data in/data/databaseis readable by that user only, and root never writes into that directory. - HTTPS from first boot. nginx serves TLS 1.2 and 1.3 with a self-signed certificate created on your instance (TLS 1.1 is refused) and sends
Strict-Transport-Security. Requests for any host name other than the configured one get their connection closed, and state-changing requests from other sites are refused, in addition to Wizarr's own CSRF protection. - Client address from the connection. nginx replaces
X-Forwarded-ForandX-Real-IPwith the real peer address and removesCF-Connecting-IPand the other forwarding headers a client could forge, so Wizarr's sign-in rate limit and its logs see the real address. - Egress fence. Wizarr connects to the media server addresses you enter. An nftables table, loaded at every boot, rejects new connections from the
wizarraccount to the instance metadata service (169.254.169.254 and fd00:ec2::254), link-local and loopback addresses, private, shared, reserved and multicast ranges, and this VPC's own IPv4 and IPv6 blocks (read from instance metadata at each boot). systemdIPAddressDenyblocks the metadata addresses for Wizarr and nginx as well. At build time, a media server URL pointing at169.254.169.254and one pointing at the instance's own VPC address both failed, from inside Wizarr. If your media server is on a private address, allow just that address withwizarr-egress-allow(Step 8); the metadata, link-local and loopback ranges cannot be allowed. - Dependency raises. Wizarr's lock file pins five packages below releases that fix published advisories. This image installs, inside the version ranges Wizarr declares and without changing any Wizarr code: brotli 1.2.1, certifi 2026.10.18, oauthlib 4.0.1, pyasn1 0.6.5 and setuptools 84.1.0.
uv pip checkreports no conflicts. The full installed set is in/usr/share/doc/wizarr-cloudimg/python-packages.txt. - Residual: expat in the Python runtime. The python-build-standalone CPython 3.13.18 build carries expat 2.9.0, which has four availability-only (denial of service) advisories fixed in expat 2.10.0; no python-build-standalone release ships the fix yet. No unauthenticated page parses XML: only responses from media servers that the administrator configures reach the XML parser. A new version of this image will follow once a fixed runtime is published.
- Build tools are not on the image. Node.js, uv, the Wizarr source tree,
node_modulesand every package cache existed only on a temporary in-memory file system during the build. No NodeSource repository and nocurl | shinstaller were used. - Hardened SSH and system. SSH allows keys only, no root login and no X11 forwarding. The AMI requires IMDSv2. SSH host keys, the machine ID and the NVMe host identity are generated fresh on every instance.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- A media server Wizarr can reach (Plex, Jellyfin, Emby, Audiobookshelf, Komga, Kavita, Navidrome or RomM), with an administrator API key or token for it
Recommended instance type: m5.large. Wizarr itself uses a few hundred MiB of memory; the media server does the heavy work and runs elsewhere.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| Wizarr 2026.10.1 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Wizarr 2026.10.1 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended) and your key pair. - Security group: the listing's default group opens SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address. Port 443 must be reachable by the people you invite, because they open their invitation links there.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDRs with your own:
SG=$(aws ec2 create-security-group --group-name wizarr \
--description "Wizarr invitations" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wizarr}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required. Associate an Elastic IP if you want the address (and the invitation links) to survive a stop and start.
Step 2: Connect and find your login
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot takes under a minute after the instance starts. It sets the name Wizarr is served as (the instance's public DNS name when it has one), creates the TLS certificate, the session key, the database and the administrator account, and writes /root/wizarr-credentials.txt (mode 0600, root). These are the entries in that file (the password is not shown here):
sudo grep -E '^(WIZARR_URL|WIZARR_ADMIN_USER)=' /root/wizarr-credentials.txt
sudo grep -oE '^[A-Z_]+=' /root/wizarr-credentials.txt
sudo stat -c '%a %U:%G %n' /root/wizarr-credentials.txt
WIZARR_URL=https://ec2-54-211-154-107.compute-1.amazonaws.com/
WIZARR_ADMIN_USER=admin
WIZARR_HOST=
WIZARR_URL=
WIZARR_ADMIN_USER=
WIZARR_ADMIN_PASSWORD=
600 root:root /root/wizarr-credentials.txt
| Value | What it is for |
|---|---|
WIZARR_HOST |
The name nginx serves Wizarr as |
WIZARR_URL |
The address of this instance's Wizarr |
WIZARR_ADMIN_USER |
The administrator's user name (admin) |
WIZARR_ADMIN_PASSWORD |
Administrator password, generated on this instance |
To read the password, run sudo cat /root/wizarr-credentials.txt in your own session and store it in your password manager.
Step 3: Confirm Wizarr is running
The first-boot units have finished, Wizarr and nginx are active, and only sshd and nginx listen beyond loopback (Wizarr's gunicorn is on 127.0.0.1:5690, the resolver on 127.0.0.53):
systemctl is-active wizarr-egress-fence.service wizarr-firstboot.service wizarr.service nginx.service
sudo ss -Htlnp | awk '{print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
head -n 3 /usr/share/doc/wizarr-cloudimg/README.cloudimg
active
active
active
active
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:5690 users:(("gunicorn"),("gunicorn"))
127.0.0.53%lo:53 users:(("systemd-resolve"))
127.0.0.54:53 users:(("systemd-resolve"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
Wizarr 2026.10.1, installed by cloudimg from the UNMODIFIED upstream release
https://codeload.github.com/wizarrrr/wizarr/tar.gz/refs/tags/v2026.10.1
sha256 25cadbb243390540a2339467a8b34c2e4071a3e7404397612ceb7e686e8515c4, tag v2026.10.1 = commit 936143363897efd3ca68568fdf6fdd9a0d63f559
Step 4: Prove the login and the web defaults
A signed-out visitor is sent to the sign-in page, the setup wizard only redirects, the API refuses a request without a key, an unknown invitation code reveals nothing, plain HTTP redirects to HTTPS, an unknown Host header gets no answer and TLS 1.1 is not offered. The last command is the image's own round-trip check: it proves anonymous access and a wrong password are refused and signs in with this instance's administrator password over HTTPS:
H=$(sudo sed -n 's/^WIZARR_HOST=//p' /root/wizarr-credentials.txt)
C=(--connect-to ::127.0.0.1:)
curl -sk "${C[@]}" -o /dev/null -w 'admin pages, signed out -> %{http_code}\n' "https://$H/admin"
curl -sk "${C[@]}" -o /dev/null -w 'setup wizard -> %{http_code}\n' "https://$H/setup/"
curl -sk "${C[@]}" -w ' <- API without a key (%{http_code})\n' "https://$H/api/status"
curl -sk "${C[@]}" "https://$H/j/NOTACODE1" | grep -o 'Invalid Invitation' | head -n 1
curl -s "${C[@]}" -o /dev/null -w 'http -> %{http_code}\n' "http://$H/"
curl -sk -o /dev/null -w 'unknown Host -> %{http_code}\n' -H 'Host: evil.example' https://127.0.0.1/ || true
curl -sk "${C[@]}" -o /dev/null -w 'TLS 1.1 -> %{http_code}\n' --tlsv1.1 --tls-max 1.1 "https://$H/" || true
sudo wizarr-selftest
admin pages, signed out -> 302
setup wizard -> 302
{"error": "Unauthorized"}
<- API without a key (401)
Invalid Invitation
http -> 301
unknown Host -> 000
TLS 1.1 -> 000
OK Wizarr self-test passed (HTTPS, anonymous refused, setup closed, wrong password refused, admin login OK)
--connect-to ::127.0.0.1: sends each request to this instance while keeping the real host name in the request, exactly as a browser would send it. 000 means the server closed the connection without an answer.
Step 5: Check the runtime and the dependency raises
The Python runtime Wizarr uses, the five raised packages as Wizarr's own environment resolves them (read as the wizarr user, exactly as the running service sees them), and the size of the installed set:
/opt/wizarr/python/bin/python3 --version
sudo -u wizarr /opt/wizarr/.venv/bin/python -c 'import importlib.metadata as m
for d in ("brotli", "certifi", "oauthlib", "pyasn1", "setuptools", "flask", "gunicorn"): print(d, m.version(d))'
wc -l < /usr/share/doc/wizarr-cloudimg/python-packages.txt
Python 3.13.18
brotli 1.2.1
certifi 2026.10.18
oauthlib 4.0.1
pyasn1 0.6.5
setuptools 84.1.0
flask 3.1.3
gunicorn 26.2.0
75
Step 6: Sign in to Wizarr
Browse to WIZARR_URL (https://<public-dns-name>/). The certificate is self-signed, so your browser warns once; accept it for this host, or install your own certificate (Step 10). Sign in with WIZARR_ADMIN_USER and WIZARR_ADMIN_PASSWORD.

After signing in you land on the dashboard, which shows your media servers, accepted invitations and recent activity once a server is connected:

Step 7: Connect your media server
Open Settings and add your media server under Media Servers: choose the server type, enter the address Wizarr should use to reach it (for example https://jellyfin.example.com or http://10.0.1.25:8096) and the server's API key or token, then let Wizarr load its libraries.

- A media server on a public address works straight away.
- A media server on a private address (in this VPC, a peered VPC or over a VPN) is blocked by the egress fence until you allow its address. Allow only the server itself, as a
/32; the change takes effect at once and survives reboots:
sudo wizarr-egress-allow add 10.0.1.25/32
sudo wizarr-egress-allow list
sudo wizarr-egress-allow remove 10.0.1.25/32
Plex sign-in for invited users goes through plex.tv, which is public, so it needs no allow entry.
Step 8: Create and send invitations
Choose Invitations to create a link: pick the server and the libraries the guest gets, how long the link is valid, how many times it can be used and how long the resulting account lasts. Wizarr shows the link (https://<your-name>/j/<code>) to copy and send.

The guest opens the link, creates an account on your media server through Wizarr's onboarding pages and is shown which apps to install. A wrong or expired code shows only this page, without any detail about your server:

Users lists everyone who joined, with the invitation they used, and lets you remove their access.
Step 9: Check the security controls
Wizarr and nginx carry IPAddressDeny for the metadata addresses, Wizarr runs sandboxed as wizarr, the egress fence is loaded with nothing allowed, the wizarr account cannot reach the metadata service on either address family, and SSH runs with the cloudimg pins:
systemctl show -p IPAddressDeny wizarr.service nginx.service
systemctl show -p User -p ProtectSystem -p ProtectHome -p NoNewPrivileges wizarr.service
sudo wizarr-egress-fence check && echo 'egress fence loaded'
sudo wizarr-egress-allow list
sudo -u wizarr curl -s -m 5 -o /dev/null -w 'metadata (IPv4) as wizarr -> %{http_code}\n' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token || true
sudo -u wizarr curl -s -g -m 5 -o /dev/null -w 'metadata (IPv6) as wizarr -> %{http_code}\n' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' 'http://[fd00:ec2::254]/latest/api/token' || true
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|x11forwarding) '
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
User=wizarr
ProtectHome=yes
ProtectSystem=strict
NoNewPrivileges=yes
egress fence loaded
(no ranges allowed: the fence is complete)
metadata (IPv4) as wizarr -> 000
metadata (IPv6) as wizarr -> 000
permitrootlogin no
passwordauthentication no
x11forwarding no
Step 10: Serve Wizarr under your own DNS name
Invitation links carry the name Wizarr is served as, so set your own name before you send them. Point a DNS record (for example wizarr.example.com) at the instance's public or Elastic IP address, then tell the image to serve that name. It creates a new self-signed certificate for the name, pins nginx to it, updates WIZARR_HOST and WIZARR_URL in the credentials file and keeps the name across reboots:
sudo wizarr-set-hostname wizarr.example.com
For a trusted certificate, obtain one for that name (for example with certbot's certonly mode using its own temporary web server, or from your organisation's CA), copy the certificate chain and key over /etc/ssl/wizarr/wizarr.crt and /etc/ssl/wizarr/wizarr.key (key mode 0600, root), and run sudo systemctl reload nginx.
Maintenance
- Rotate the administrator password and the session key:
sudo wizarr-rotate-credentialssets a new password and a newSECRET_KEY, signs every browser out, updates the credentials file and restarts Wizarr. Invitations, users and settings are kept. If you change the password from Wizarr's own interface instead, the credentials file is not updated. - Operating system updates: unattended security upgrades are on. Reboot when
/var/run/reboot-requiredexists; Wizarr and nginx start again by themselves. - Wizarr updates: Wizarr is installed read-only under
/opt/wizarr. To move to a newer release, launch the newer version of this image when the listing offers it and copy/data/databaseacross (stop Wizarr on both first). - Backups: everything Wizarr keeps is in
/data/database: the SQLite database (database.db), the session key (secrets.json) and the session files. Stop Wizarr (sudo systemctl stop wizarr) and copy the directory, or take an EBS snapshot of the root volume. - Logs:
sudo journalctl -u wizarrfor Wizarr,/var/log/nginx/for web requests,sudo journalctl -u wizarr-firstbootfor first boot. - What the instance contacts: Wizarr connects to the media servers you configure, to plex.tv for Plex sign-in, and once a day to
https://update.wizarr.devfor the "update available" notice (it sends no usage data and installs nothing). All of it passes the egress fence. Everything else comes from the operating system: the AWS Systems Manager agent, snapd, unattended upgrades and the time service. Disable the SSM agent if you do not use Session Manager.
Troubleshooting
- The web page does not load right after launch: first boot is still running.
sudo journalctl -u wizarr-hostname.service -u wizarr-firstboot.service -fshows its progress. - Browser shows "connection closed": nginx answers only the configured name. Use
WIZARR_URL, or runsudo wizarr-set-hostname <name>for a new name. - Wizarr cannot reach a media server on a private address: the egress fence blocks private ranges and this VPC. Allow the server's address with
sudo wizarr-egress-allow add <ip>/32(Step 7). - Wizarr does not start:
sudo journalctl -u wizarr -n 20shows the start guard's reason, for example a drop-in that setDISABLE_BUILTIN_AUTH=trueor moved gunicorn off127.0.0.1. - Too many sign-in attempts: Wizarr allows ten sign-in attempts a minute; wait a minute and try again.
- The end-to-end check:
sudo wizarr-selftestprintsOK Wizarr self-test passedwhen everything works.
Licensing
Wizarr is licensed under the MIT licence. The licence text is on the instance in /usr/share/doc/wizarr-cloudimg/LICENSE.md, and /usr/share/doc/wizarr-cloudimg/README.cloudimg records the exact upstream release and how the image was built. The source is at github.com/wizarrrr/wizarr. Wizarr Plus is not included. cloudimg is not affiliated with, endorsed by or sponsored by the Wizarr project or its authors, or by Plex, Jellyfin, Emby or the other media servers named here.
Support
cloudimg provides 24/7 technical support for this image by email and live chat: deployment, first login, media server connection, HTTPS and DNS setup, upgrades and troubleshooting. Contact support@cloudimg.co.uk.