Jellystat on Ubuntu 24.04 on Azure User Guide
Overview
Jellystat is a popular open source, self hosted statistics and analytics dashboard for a Jellyfin media server. It records playback activity and presents your libraries, users, full and grouped watch history, most active content and live now playing sessions in a clean, modern interface with charts and drill downs.
The cloudimg image builds Jellystat 1.1.11 from source under /opt/jellystat, runs it as the dedicated jellystat system user bound to loopback 127.0.0.1:3000, and fronts it with nginx on port 80. Its datastore is a local PostgreSQL database (jfstat) bound to loopback, so nothing listens on a public interface except nginx. nginx is configured for the Socket.io WebSocket traffic the live now playing view relies on and is ready for your TLS certificate.
Secure by default: the image ships uninitialised, with no administrator and no shared secret. Your first visit lands on the create admin setup wizard where you choose your own admin username and password, then add your Jellyfin server. Two per VM secrets, a JWT signing secret and the PostgreSQL role password, are generated automatically on first boot and never shared between instances. A jellystat-firstboot.service oneshot rotates those secrets, ensures the empty database exists, resolves the instance URL and starts the stack, then disables itself.
What is included:
- Jellystat 1.1.11 (MIT) under
/opt/jellystat - Local PostgreSQL 16 database
jfstat, bound to loopback (127.0.0.1:5432) jellystat.servicerunning asjellystat, bound to127.0.0.1:3000- nginx reverse proxy on port 80, Socket.io WebSocket aware, ready for TLS
jellystat-firstboot.servicefor per VM secret rotation and stack start- Node.js 22 LTS runtime
- Ubuntu 24.04 LTS base, fully patched
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet with a subnet. Recommended VM size: Standard_B2s (Jellystat is lightweight; 4 GB RAM is comfortable). You will also need a reachable Jellyfin server and a Jellyfin API key so Jellystat can read playback data during setup.
Step 1: Deploy from the Azure Portal
Search the Marketplace for Jellystat on Ubuntu 24.04, choose your VM size, and attach an NSG that allows TCP 22 (SSH) from your management network and TCP 80 / 443 (the web app) from the networks that need it. Front port 80 with TLS in production (see the HTTPS section below).
Step 2: Deploy from the Azure CLI
RG="jellystat-prod"; LOCATION="eastus"; VM_NAME="jellystat-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/jellystat-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name js-vnet --address-prefix 10.90.0.0/16 --subnet-name js-subnet --subnet-prefix 10.90.1.0/24
az network nsg create -g "$RG" --name js-nsg
az network nsg rule create -g "$RG" --nsg-name js-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name js-nsg --name allow-web --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 443 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name js-vnet --subnet js-subnet --nsg js-nsg --public-ip-sku Standard
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
Step 4: Verify the services are running
PostgreSQL and Jellystat both listen on loopback only; nginx fronts the web app on port 80 and proxies its Socket.io WebSocket traffic.
sudo systemctl is-active postgresql jellystat nginx
ss -tln | grep -E ':80 |127.0.0.1:3000|127.0.0.1:5432'
curl -s -o /dev/null -w 'GET /auth/isconfigured -> HTTP %{http_code}\n' http://127.0.0.1/auth/isconfigured
All three services report active, PostgreSQL is bound to 127.0.0.1:5432, the Node server to 127.0.0.1:3000, nginx to :80, and the configuration API returns HTTP 200 through nginx.

Step 5: Secure by default, no admin and no secret ships
Nothing sensitive ships in the image. The application reports state:0, so every instance starts at the create admin wizard, and the per VM credentials note in /root is root only. Two per VM secrets, the JWT signing secret and the PostgreSQL password, are generated on first boot; they are masked below.
curl -s http://127.0.0.1/auth/isconfigured
sudo stat -c '%n perms=%a owner=%U:%G' /root/jellystat-credentials.txt
sudo sed -E 's/^(JWT_SECRET|POSTGRES_PASSWORD)=.*/\1=**** (per-VM, first boot)/' /root/jellystat-credentials.txt | grep -E '^JELLYSTAT_URL|^POSTGRES_USER|^POSTGRES_DB|^NOTE'
curl -s -o /dev/null -w 'GET /api/getLibraries (no token) -> HTTP %{http_code}\n' http://127.0.0.1/api/getLibraries
The app reports state:0 (create admin wizard), the credentials note is 600 root:root, the two secrets are unique to this VM, and a protected API endpoint returns HTTP 401 without an auth token.

Step 6: Complete the first run setup wizard
Open http://<vm-ip>/ in your browser. Because no administrator ships, Jellystat shows First Time Setup Step 1 of 2: choose your own admin username and password and click Create User.

Step 2 of 2 asks for your Jellyfin server URL and an API key so Jellystat can read playback data. Enter your server URL and a Jellyfin API key (create one in Jellyfin under Dashboard, API Keys), then click Save Jellyfin Details.

Step 7: Log in and explore the dashboard
After setup, Jellystat requires authentication. Sign in with the admin username and password you just created.

You land on the dashboard, which shows live now playing sessions, recently added media, watch statistics over a configurable window, and a library overview. The left navigation gives you Libraries, Users, Activity, Statistics and Settings.

Step 8: The runtime stack
node -v
nginx -v
sudo runuser -u postgres -- psql -tAc 'show server_version'
jq -r .version /opt/jellystat/package.json
Node.js 22 LTS runs the server, nginx 1.24 fronts it, PostgreSQL 16 stores the data, and the pinned Jellystat release is 1.1.11.

Step 9: Data and persistence
All statistics live in the local PostgreSQL database jfstat, captured into the image and carried onto every instance. The application creates its own schema on first connect.
sudo runuser -u postgres -- psql -tAc "SELECT datname FROM pg_database WHERE datname='jfstat'"
systemctl is-enabled jellystat-firstboot.service
apt-mark showhold
The jfstat database is present, the first boot service has run and disabled itself for this instance, and there are no held packages, so the OS security baseline is intact.

Step 10: Enable HTTPS
nginx already fronts Jellystat on port 80 and is ready for TLS. Point a DNS record at your VM, then obtain a certificate with certbot:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> --agree-tos -m <your-email> --redirect
certbot installs the certificate, rewrites the nginx server block for TLS, and sets up automatic renewal. Your analytics portal is then served over HTTPS.
Managing the service
sudo systemctl status jellystat --no-pager
sudo systemctl restart jellystat
sudo journalctl -u jellystat -n 20 --no-pager
The Node server logs to the journal. nginx logs are under /var/log/nginx/. The PostgreSQL data lives under /var/lib/postgresql. Back up the instance by snapshotting the OS disk, or use Jellystat's own scheduled backup feature under Settings.
Support
cloudimg provides 24/7 support for this image via email and live chat: deployment, nginx TLS termination, upgrades, Jellyfin integration and API keys, PostgreSQL tuning and backups. Email support@cloudimg.co.uk.
This is a repackaged open source software product with additional charges for cloudimg support services. Jellystat and Jellyfin are trademarks of their 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.