miniserve on Ubuntu 24.04 on Azure User Guide
Overview
miniserve is a small, fast, self hosted HTTP file server. Point it at a directory and it serves a clean web interface to browse the folder, download individual files or whole folders as tar.gz or zip archives, and upload new files or create directories straight from the browser. It is a single static Rust binary with no database and no accounts system, so it starts instantly and stays lightweight.
Upstream miniserve is public with no authentication. The cloudimg image inverts that: HTTP basic authentication is required, miniserve binds only to loopback behind an nginx reverse proxy, and it serves only a dedicated directory at /srv/miniserve rather than the whole filesystem. There is no default password. The per VM password is generated uniquely on each virtual machine's first boot by miniserve-firstboot.service; only its sha256 hash is stored in the service configuration and the plaintext is written to a root only file at /root/miniserve-info.txt. It is never baked into the image.
What is included:
- miniserve 0.35.0 single static Rust binary (
/usr/local/bin/miniserve) - A dedicated served directory at
/srv/miniservewith file upload and directory creation enabled miniserve.servicerunning asminiserve, bound to loopback127.0.0.1:8080, with HTTP basic auth requirednginx.servicereverse proxy on TCP 80, TLS ready, sized for large uploadsminiserve-firstboot.servicegenerating a per VM basic auth password on first boot (usernamecloudimg)- Folder downloads as tar.gz or zip, light and dark themes, and browser uploads
- Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key, and a VNet with a subnet. Recommended VM size: Standard_B2s (miniserve is very light; 4 GB RAM is plenty).
Step 1: Deploy from the Azure Portal
Search the Azure Marketplace for miniserve, choose the plan, and create the VM. In the networking step attach an NSG that allows inbound TCP 22 (SSH) and TCP 80 (the web UI) from your client networks only. Put a TLS reverse proxy in front of port 80 for production.
Step 2: Deploy from the Azure CLI
RG="miniserve-prod"; LOCATION="eastus"; VM_NAME="miniserve-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/miniserve-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 ms-vnet --address-prefix 10.104.0.0/16 --subnet-name ms-subnet --subnet-prefix 10.104.1.0/24
az network nsg create -g "$RG" --name ms-nsg
az network nsg rule create -g "$RG" --nsg-name ms-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 ms-nsg --name allow-web --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --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 ms-vnet --subnet ms-subnet --nsg ms-nsg --public-ip-sku Standard
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
miniserve.service, nginx.service and miniserve-firstboot.service all start automatically on first boot.
Step 4: Verify the Service
sudo systemctl is-active miniserve nginx miniserve-firstboot
sudo test -f /var/lib/cloudimg/miniserve-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tlnp | grep -E ':80 |:8080 '
Expected output — all three services are active, nginx is bound to the public port 80 and miniserve is bound to loopback 127.0.0.1:8080 only:
active
active
active
FIRSTBOOT_DONE
LISTEN 0 1024 127.0.0.1:8080 0.0.0.0:* users:(("miniserve",...))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",...))

Step 5: Retrieve the Per VM Login
There is no default password. On first boot a per VM password is generated for the cloudimg user; only its sha256 hash is stored in the service configuration, and the plaintext is written to a root only note:
sudo cat /root/miniserve-info.txt
The note contains the username, the per VM password (unique to this machine, never baked into the image) and the resolved URL:
MINISERVE_USERNAME=cloudimg
MINISERVE_PASSWORD=<MINISERVE_PASSWORD>
MINISERVE_URL=http://<vm-ip>/
NOTE=Open MINISERVE_URL in a browser and log in with MINISERVE_USERNAME / MINISERVE_PASSWORD to browse, download and upload files under /srv/miniserve.
Step 6: Secure by Default
miniserve binds only to loopback and is fronted by nginx. Basic auth is enforced by miniserve itself, and the service configuration stores only the sha256 hash of the password — never the plaintext:
grep '^MINISERVE_AUTH=' /etc/miniserve/miniserve.env
sudo ss -tlnp | grep 8080
grep -E 'client_max_body_size|proxy_pass' /etc/nginx/sites-available/cloudimg-miniserve
The auth line carries the username and a sha256 hash, miniserve listens on loopback only, and nginx forwards requests to it:
MINISERVE_AUTH=cloudimg:sha256:<sha256 hash of the per VM password>
LISTEN 0 1024 127.0.0.1:8080 0.0.0.0:* users:(("miniserve",...))
client_max_body_size 5g;
proxy_pass http://miniserve_backend;

Step 7: Prove Auth Is Enforced and a File Round Trips
Without a login the listing is refused with HTTP 401; the unauthenticated healthcheck stays open so a load balancer can probe it:
curl -s -o /dev/null -w 'no login: HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'healthcheck: HTTP %{http_code}\n' http://127.0.0.1/__miniserve_internal/healthcheck
Log in with the per VM password to list the directory, then upload a file through the /upload endpoint and download it back:
sudo bash -c '
P=$(grep "^MINISERVE_PASSWORD=" /root/miniserve-info.txt | cut -d= -f2-)
curl -s -o /dev/null -w "listing (with login): HTTP %{http_code}\n" -u cloudimg:$P http://127.0.0.1/
echo "hello from cloudimg" > /tmp/report.txt
curl -s -o /dev/null -w "upload: HTTP %{http_code}\n" -u cloudimg:$P -F "file=@/tmp/report.txt;filename=report.txt" "http://127.0.0.1/upload?path=."
echo "download: $(curl -s -u cloudimg:$P http://127.0.0.1/report.txt)"
rm -f /srv/miniserve/report.txt /tmp/report.txt
'
The listing returns 200 with the login, the upload returns 303, and the file downloads back byte for byte:
no login: HTTP 401
healthcheck: HTTP 200
listing (with login): HTTP 200
upload: HTTP 303
download: hello from cloudimg

From your own machine, use the public URL and the per VM password instead of loopback:
curl -u cloudimg:<MINISERVE_PASSWORD> http://<vm-ip>/
curl -u cloudimg:<MINISERVE_PASSWORD> -F 'file=@./photo.jpg' 'http://<vm-ip>/upload?path=.'
Step 8: Browse in the Web UI
Open the URL in a browser and log in as cloudimg with the per VM password:
open http://<vm-ip>/
The directory listing shows every file and folder with sizes and timestamps, buttons to download the whole directory as tar.gz or zip, a theme picker, and a search box. Directories are listed alongside files and can be opened to browse deeper.

Step 9: Upload Files and Create Directories
The upload form sits above the listing. Choose one or more files, or drag them anywhere into the window, then select Upload file. A directory name field next to it creates new folders. Uploads land directly in the served directory:

Open a subfolder to browse into it; the breadcrumb at the top and the Parent directory row let you navigate back up:

Step 10: Themes
miniserve ships light and dark colour schemes. Use the Change theme control, or let it follow your browser's light or dark preference automatically:

Step 11: Server Components
| Component | Path |
|---|---|
| miniserve binary | /usr/local/bin/miniserve |
| Served directory | /srv/miniserve |
| Config (EnvironmentFile) | /etc/miniserve/miniserve.env |
| Systemd unit | /etc/systemd/system/miniserve.service |
| Firstboot script | /usr/local/sbin/miniserve-firstboot.sh |
| Per VM info note | /root/miniserve-info.txt (mode 0600) |
| Sentinel | /var/lib/cloudimg/miniserve-firstboot.done |
/usr/local/bin/miniserve --version
ls /usr/local/bin/miniserve /etc/miniserve/miniserve.env /etc/systemd/system/miniserve.service

Step 12: Managing the Service
sudo systemctl restart miniserve.service
sudo journalctl -u miniserve.service --no-pager -n 20
miniserve refuses to start unless a valid MINISERVE_AUTH credential is present, so it never serves files without a login even if first boot has not run.
Step 13: Configuration
miniserve is configured entirely through environment variables in /etc/miniserve/miniserve.env. Useful keys already set by the image:
| Variable | Meaning | Image default |
|---|---|---|
MINISERVE_PATH |
Directory served | /srv/miniserve |
MINISERVE_INTERFACE |
Bind address | 127.0.0.1 (loopback) |
MINISERVE_PORT |
Bind port | 8080 |
MINISERVE_AUTH |
Basic auth credential | cloudimg:sha256:... (per VM, at first boot) |
MINISERVE_ALLOWED_UPLOAD_DIR |
Upload target within the served dir | / (uploads land in the served directory) |
MINISERVE_MKDIR_ENABLED |
Allow creating directories | true |
MINISERVE_ENABLE_TAR_GZ / MINISERVE_ENABLE_ZIP |
Folder archive downloads | true |
MINISERVE_TITLE |
Page title | cloudimg file server |
After editing the file, apply changes with sudo systemctl restart miniserve.service. To rotate the password, set a new MINISERVE_AUTH line to cloudimg:sha256:<hash> where the hash is printf '%s' 'newpassword' | sha256sum, then restart the service.
Step 14: Add TLS (Optional)
For production, terminate TLS at nginx. Point a DNS record at the VM, then use the packaged nginx with a certificate from Let's Encrypt:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain>
certbot installs the certificate, rewrites the nginx server block to listen on 443 and sets up automatic renewal.
Step 15: Security Recommendations
- Restrict the NSG so ports 80 and 22 only reach trusted networks.
- Terminate TLS at nginx (Step 14) so file transfers and the login travel encrypted.
- Keep basic auth on; the per VM password protects the listing, uploads and downloads. Rotate it from
/etc/miniserve/miniserve.env(Step 13). - Serve only
/srv/miniserve; the image deliberately serves one dedicated directory, never the whole filesystem. - Patch the OS monthly with
sudo apt-get update && sudo apt-get upgrade; unattended security upgrades are already enabled.
Step 16: Support and Licensing
miniserve is MIT licensed — no per CPU or per user fee. cloudimg provides commercial support separately.
- Email: support@cloudimg.co.uk
- Website: www.cloudimg.co.uk
- Support hours: 24/7, 24h response SLA