Storage AWS

miniserve 0.35.0 on AWS User Guide

| Product: miniserve 0.35.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the miniserve 0.35.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg SFTP and file server listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

miniserve is a small, fast, self hosted HTTP file server. It serves one directory through a clean web interface: browse folders, download single files or whole folders as tar.gz or zip archives, upload files from the browser and create directories. It is a single static Rust binary with no database, so it starts instantly and stays light.

Upstream miniserve is public with no login. The cloudimg image turns that around:

  • HTTPS from the first boot. nginx serves the web interface on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • A password unique to your instance. HTTP Basic Auth is required for the user cloudimg. The password is generated on first boot and written to /root/miniserve-info.txt; only its sha256 hash is stored in the service configuration. No password is baked into the image.
  • Fail closed. miniserve refuses to start until first boot has written that credential, and nginx refuses to start until first boot has finished, so the files are never served without a login.
  • One directory only. miniserve serves /srv/miniserve, never the whole filesystem, and listens on loopback 127.0.0.1:8080 behind nginx.
  • Hardened defaults. miniserve runs as its own unprivileged user in a systemd sandbox, both miniserve and nginx are blocked from the instance metadata service, the AMI requires IMDSv2, and requests for any host name the instance does not serve are dropped.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: miniserve 0.35.0 (/usr/local/bin/miniserve, the official release binary, checksum verified), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (the web interface) and TCP 80 (redirects to HTTPS) from your own networks only.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick miniserve 0.35.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (miniserve itself needs very little; size for your users and file sizes), your key pair, and a security group as above. Add an EBS volume size that fits the files you plan to store; the served directory lives on the root volume.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=miniserve}]'

Step 2: Connect to your instance

Version SSH user
miniserve 0.35.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and the password, then starts miniserve and nginx:

systemctl is-active miniserve nginx miniserve-firstboot
sudo test -f /var/lib/cloudimg/miniserve-firstboot.done && echo FIRSTBOOT_DONE

All three units are active and the first boot marker is present:

active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on public addresses; miniserve listens on loopback:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sort
0.0.0.0:22 users:(("sshd",...))
0.0.0.0:443 users:(("nginx",...))
0.0.0.0:80 users:(("nginx",...))
127.0.0.1:8080 users:(("miniserve",...))
[::]:22 users:(("sshd",...))
[::]:443 users:(("nginx",...))
[::]:80 users:(("nginx",...))

Step 4: Get your URL and password

sudo cat /root/miniserve-info.txt

The file is readable by root only and holds the URL, the user name and this instance's password:

MINISERVE_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
MINISERVE_USERNAME=cloudimg
MINISERVE_PASSWORD=<MINISERVE_PASSWORD>

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve the files under your own DNS name, see Step 9.

Step 5: Sign in from your browser

Open the MINISERVE_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 9). Sign in as cloudimg with the password from Step 4.

The listing shows every file and folder in /srv/miniserve with its size and date, buttons to download the whole folder as tar.gz or zip, a search box and a theme picker. The image ships with a README.txt and an examples/ folder; replace them with your own files.

The miniserve directory listing titled cloudimg file server, with download tar.gz and zip buttons, the upload and create directory controls, and the documents and examples folders, inventory.csv and README.txt

Step 6: Upload files and create folders

Choose one or more files, or drag them anywhere into the window, then select Upload file. Type a name next to Create directory to add a folder. Uploads land in the folder you are viewing:

The miniserve upload control with meeting-notes.txt selected and ready to upload, next to the create directory control

Open a folder to browse into it; the breadcrumb and the Parent directory row take you back up:

Browsing the documents folder, with the cloudimg file server breadcrumb, a Parent directory row and two text files

miniserve follows your browser's light or dark preference, and Change theme picks another colour scheme:

The miniserve listing in the dark colour scheme

Files you upload are owned by the miniserve user. An upload with the same name as an existing file is refused rather than overwriting it.

Step 7: Use it from the command line

Without a login the listing is refused, port 80 redirects to HTTPS, and the health check stays open for load balancers:

H=$(sudo grep '^MINISERVE_URL=' /root/miniserve-info.txt | sed -E 's#^MINISERVE_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http://  -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'healthcheck -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/__miniserve_internal/healthcheck"
http://  -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
no login -> HTTP 401
healthcheck -> HTTP 200

With the password, upload a file and download it back. The password is passed to curl on standard input, so it never appears in the process list or your shell history:

sudo bash -c '
  F=/root/miniserve-info.txt
  P=$(grep "^MINISERVE_PASSWORD=" $F | cut -d= -f2-)
  H=$(grep "^MINISERVE_URL=" $F | sed -E "s#^MINISERVE_URL=https://([^/]+)/.*#\1#")
  R="--resolve $H:443:127.0.0.1"
  auth() { printf "user = \"cloudimg:%s\"\n" "$P"; }
  echo "hello from cloudimg" > /root/report.txt
  auth | curl -K - -sk $R -o /dev/null -w "listing: HTTP %{http_code}\n" "https://$H/"
  auth | curl -K - -sk $R -o /dev/null -w "upload:  HTTP %{http_code}\n" -F "file=@/root/report.txt" "https://$H/upload?path=/"
  echo "download: $(auth | curl -K - -sk $R "https://$H/report.txt")"
  rm -f /root/report.txt /srv/miniserve/report.txt
'
listing: HTTP 200
upload:  HTTP 303
download: hello from cloudimg

From your own machine, use the URL and password from Step 4 (-k accepts the self signed certificate):

curl -k -u cloudimg:<MINISERVE_PASSWORD> https://<public-ip>/
curl -k -u cloudimg:<MINISERVE_PASSWORD> -F 'file=@./photo.jpg' 'https://<public-ip>/upload?path=/'

You can also copy files straight into /srv/miniserve over SSH; make them readable by the miniserve user (sudo chown -R miniserve:miniserve /srv/miniserve).

Step 8: Security checks

Only the sha256 hash of the password is in the service configuration:

sudo grep '^MINISERVE_AUTH=' /etc/miniserve/miniserve.env | sed -E 's/[0-9a-f]{64}/<sha256>/'
MINISERVE_AUTH=cloudimg:sha256:<sha256>

The certificate was generated on this instance and names its own addresses:

H=$(sudo grep '^MINISERVE_URL=' /root/miniserve-info.txt | sed -E 's#^MINISERVE_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
    DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

miniserve runs unprivileged in a sandbox, and neither miniserve nor nginx can reach the instance metadata service:

systemctl show miniserve -p User -p ProtectSystem -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
User=miniserve
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32

nginx passes miniserve a fixed host name and the real client address from the connection, never values a client sends:

grep -E 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP)' /etc/nginx/sites-available/cloudimg-miniserve
        proxy_set_header Host 127.0.0.1:8080;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;

nginx also refuses an upload whose Origin is another web site (HTTP 403), because browsers resend Basic Auth credentials on cross site requests.

Step 9: Your own DNS name and certificate

Point a DNS record at the instance, then set the name miniserve is served as. This mints a new self signed certificate for the name, updates nginx and the URL in /root/miniserve-info.txt, and keeps the name across reboots:

sudo miniserve-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running miniserve-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/miniserve/miniserve.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/miniserve/miniserve.key
sudo nginx -t && sudo systemctl reload nginx

Step 10: Configuration and password rotation

miniserve reads /etc/miniserve/miniserve.env. The image sets:

Variable Meaning Image default
MINISERVE_PATH Directory served /srv/miniserve
MINISERVE_INTERFACE / MINISERVE_PORT Bind address and port 127.0.0.1 / 8080 (nginx fronts it)
MINISERVE_AUTH Basic Auth credential cloudimg:sha256:... (generated on first boot)
MINISERVE_ALLOWED_UPLOAD_DIR Where uploads are allowed / (anywhere 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

Run miniserve --help for every option (each lists its environment variable). After editing the file, run sudo systemctl restart miniserve.

To set a new password, store its sha256 hash and restart (start the first line with a space so most shells keep it out of your history):

 HASH=$(printf '%s' '<new-password>' | sha256sum | awk '{print $1}')
sudo sed -i -E "s/^MINISERVE_AUTH=.*/MINISERVE_AUTH=cloudimg:sha256:${HASH}/" /etc/miniserve/miniserve.env
sudo systemctl restart miniserve

Then update or delete MINISERVE_PASSWORD in /root/miniserve-info.txt so it does not hold an old password. Uploads are limited to 5 GB each by client_max_body_size in /etc/nginx/sites-available/cloudimg-miniserve; change it there and reload nginx.

Step 11: Server components

Component Path
miniserve binary /usr/local/bin/miniserve
Served directory /srv/miniserve
Configuration /etc/miniserve/miniserve.env (root only)
Service unit /etc/systemd/system/miniserve.service
nginx site /etc/nginx/sites-available/cloudimg-miniserve
Certificate and key /etc/ssl/miniserve/
First boot script /usr/local/sbin/miniserve-firstboot.sh
Host name helper /usr/local/sbin/miniserve-set-hostname
Credentials /root/miniserve-info.txt (mode 0600)
/usr/local/bin/miniserve --version
sudo journalctl -u miniserve -u nginx --no-pager -n 5 -o cat

Step 12: Security recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Keep the per instance password secret, and rotate it if it may have been shared (Step 10).
  • Use your own DNS name and a certificate your clients trust (Step 9).
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.
  • Snapshot the root volume, or copy /srv/miniserve elsewhere, to back up your files.

Support and licensing

miniserve is open source under the MIT licence; there is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA