Wizarr 2026.10.1
Overview
Wizarr is a free, self-hosted invitation and onboarding manager for media servers, released under the MIT license. You connect it to your Plex, Jellyfin, Emby, Audiobookshelf, Komga, Kavita, Navidrome or RomM server. You then 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.
This guide covers launching and verifying Wizarr 2026.10.1 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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.
What you get:
- 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 - An 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 theadministrator. On this image, first boot creates the single administrator admin through Wizarr's own models, before Wizarr or nginx start. The password is generated on your instance and handed over on standard input, never on a command line./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, and 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. - Fail-closed startup. Wizarr and nginx start only after first boot has finished. A start guard runs before every Wizarr start and refuses to start it in any of these cases: 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 the wizarr account open connections to it. Wizarr runs as the unprivileged wizarr user 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. 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. It 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). systemd IPAddressDeny blocks 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 7). 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 newer releases, 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-sac/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, with 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 that 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
Launch the instance
Option A: AWS Marketplace console
- Open the SecureAnyCloud Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace. 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. - Choose a 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. Replace 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, because the AMI already requires IMDSv2. Associate an Elastic IP if you want the address (and the invitation links) to survive a stop and start.
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). It creates the TLS certificate, the session key, the database and the administrator account, then writes /root/wizarr-credentials.txt (mode 0600, root). The commands below list the entries in that file without showing the password:
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.txtWIZARR_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.txtValue 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. Store the password in your password manager.
Confirm Wizarr is running
Confirm that the first-boot units have finished, that Wizarr and nginx are active, and that only sshd and nginx listen beyond loopback. Wizarr's gunicorn is on 127.0.0.1:5690, and the resolver is 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-sac/README.sacactive 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 sac 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 936143363897efd3ca68568fdf6fdd9a0d63f559Prove the login and the web defaults
These commands show that a signed-out visitor is sent to the sign-in page, and that the setup wizard only redirects. They also show that 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 that anonymous access and a wrong password are refused, and it signs in over HTTPS with this instance's administrator password:
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-selftestadmin 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)The --connect-to ::127.0.0.1: option sends each request to this instance while keeping the real host name in the request, exactly as a browser would send it. A result of 000 means the server closed the connection without an answer.
Check the runtime and the dependency raises
This command prints the Python runtime Wizarr uses, the five raised packages as Wizarr's own environment resolves them, and the size of the installed set. It reads the packages as the wizarr user, exactly as the running service sees them:
/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-sac/python-packages.txtPython 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 75Sign 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 you sign in, you land on the dashboard. Once a server is connected, it shows your media servers, accepted invitations and recent activity.
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/32Plex sign-in for invited users goes through plex.tv, which is public, so it needs no allow entry.
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>) for you 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 an error page, without any detail about your server.
Users lists everyone who joined, with the invitation they used, and lets you remove their access.
Check the security controls
These commands confirm the following. 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. SSH runs with the SecureAnyCloud 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 noServe 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. The command creates a new self-signed certificate for the name and pins nginx to it. It also updates WIZARR_HOST and WIZARR_URL in the credentials file and keeps the name across reboots:
sudo wizarr-set-hostname wizarr.example.comFor a trusted certificate, obtain one for that name, for example with certbot's certonly mode using its own temporary web server, or from your organization's CA. Copy the certificate chain and key over /etc/ssl/wizarr/wizarr.crt and /etc/ssl/wizarr/wizarr.key (key mode 0600, root), then 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. - Update the operating system. Unattended security upgrades are on. Reboot when
/var/run/reboot-requiredexists. Wizarr and nginx start again by themselves. - Update Wizarr. 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 instances first. - Back up. 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. - Read the logs. Use
sudo journalctl -u wizarrfor Wizarr,/var/log/nginx/for web requests, andsudo journalctl -u wizarr-firstbootfor first boot. - Know 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. That check sends no usage data and installs nothing. All of this traffic 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. - The browser shows "connection closed". nginx answers only the configured name. Use
WIZARR_URL, or runsudo wizarr-set-hostname <name>to set 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.
- Run the end-to-end check.
sudo wizarr-selftestprintsOK Wizarr self-test passedwhen everything works.
Licensing
Wizarr is licensed under the MIT license. The license text is on the instance at /usr/share/doc/wizarr-sac/LICENSE.md, and /usr/share/doc/wizarr-sac/README.sac records the exact upstream release and how the image was built. The source is at github.com/wizarrrr/wizarr. Wizarr Plus is not included.
SecureAnyCloud 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
SecureAnyCloud provides 24/7 technical support for this image by email and live chat. Support covers deployment, first login, media server connection, HTTPS and DNS setup, upgrades and troubleshooting. Contact sales@secureanycloud.com.
Deploy on AWS
Launch Wizarr 2026.10.1 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