Applications Azure

Directory Lister on Ubuntu 24.04 on Azure User Guide

| Product: Directory Lister 5.6.1 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Directory Lister on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Directory Lister is a lightweight web application that turns a directory of files into a clean, styled listing in the browser, published at github.com/DirectoryLister/DirectoryLister. It needs no database: it reads files straight from the filesystem and renders a browsable listing with breadcrumb navigation, file search, inline README rendering, single-file downloads and on-the-fly zip downloads of whole folders.

The image installs Directory Lister 5.6.1 served by nginx and PHP 8.3 FPM, with the application running under a dedicated non-root service account. Unattended security upgrades are configured to keep the server patched on your running VM.

One appliance, one web application. nginx and php8.3-fpm serve the whole site on port 80. There is no database and no mail engine — port 80 is the only network-reachable service.

A safe served root, and hardened for a file browser. The appliance serves a dedicated, self-contained demo directory (/var/www/dirlister/files), never the server filesystem, the application's own source, or any sensitive path. Directory Lister protects file access through PHP open_basedir, so this image is arranged so that the served directory is the open_basedir ceiling: the application code lives in a hidden subdirectory inside the served root. As a result:

  • Path traversal above the served root is refused. Relative paths (../), URL-encoded sequences and symlinks that point outside the served directory all return 404 — no file outside the served directory can be read.
  • The application's own configuration, environment file and source are never listed or served. They are excluded from the listing, from search and from zip archives, and return 404 over HTTP.
  • nginx never renders a raw directory index — only Directory Lister's own rendered listing of the served files is shown.

Public read-only by design — no credential ships in the image. The demo listing is public and read-only; there is no login and no secret anywhere in the image. If you want a private listing you can enable Directory Lister's built-in password protection yourself (see Step 13). On first boot a one-shot service prepares each instance (it resolves the VM's address and writes an information file), then a bootstrap marker lets nginx and php8.3-fpm start; until that marker exists systemd skips those units, so the site never serves a half-prepared instance. The units are enabled, so the appliance comes straight back after a reboot.

What is included:

  • Directory Lister 5.6.1 served by nginx and PHP 8.3 FPM, running under a dedicated non-root service account, with a strict nginx allowlist that serves only the compiled front-end assets and the application's rendered listing
  • A safe, self-contained demo directory as the served root, with the served root as the open_basedir ceiling so traversal above it is refused
  • A first-boot service that prepares each instance and a bootstrap gate that holds the web services until preparation is complete

Prerequisites

  • An Azure subscription and either the Azure Portal or the Azure CLI (az) signed in
  • An SSH key pair so you can administer the VM
  • Inbound TCP port 80 open to the browsers that will reach the listing (and port 22 for administration)

Step 1: Deploy from the Azure Portal

  1. Open the cloudimg Directory Lister on Ubuntu 24.04 LTS offer in the Azure Marketplace and choose Create.
  2. Pick a resource group and region, keep the recommended Standard_B2s size (Directory Lister is tiny and runs comfortably on 2 vCPU / 4 GB), and provide your SSH public key for the azureuser account.
  3. On the Networking step allow inbound 80 (the web application) and 22 (SSH). Directory Lister serves over port 80; put it behind an Azure Application Gateway or a reverse proxy if you want TLS.
  4. Create the VM, then browse to its public IP address to see the demo directory listing.

Step 2: Deploy from the Azure CLI

# Replace <sub-id> and <version> with your subscription and the image version.
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/directory-lister-ubuntu-24-04/versions/<version>"

az group create --name directory-lister-rg --location eastus

az vm create \
  --resource-group directory-lister-rg \
  --name directory-lister \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

az vm open-port --resource-group directory-lister-rg --name directory-lister --port 80

Step 3: First boot and the appliance information

On first boot a one-shot service resolves the VM's address and writes an information file. There is no credential — the listing is public and read-only by design. Read the information file over SSH:

sudo cat /root/directory-lister-info.txt

It records the listing URL and the served directory:

DIRECTORY_LISTER_URL=http://<your-service-ip>/
DIRECTORY_LISTER_SERVED_ROOT=/var/www/dirlister/files

Step 4: Confirm the appliance is running

Both web services are active, PHP 8.3 and nginx report their versions, the application runs as the dedicated non-root dirlister account, and port 80 is the only listener:

sudo systemctl is-active php8.3-fpm nginx
php -v | head -1
nginx -v
id dirlister
sudo ss -tulnp | grep ':80 '

Both services report active, PHP 8.3 and nginx report their versions, the php-fpm worker runs as the dedicated non-root dirlister account, and ss shows the web application on port 80 as the only listener

Step 5: Browse the demo directory

Browse to the VM's address to see the demo directory rendered as a clean, styled listing: the data, documents and images folders, the README.md file (rendered inline), each entry with its size and modified date, a search box, and a download control.

The Directory Lister home page rendering the browsable demo directory listing with the data, documents and images folders and an inline README

Step 6: Open a folder and download files

Click a folder to browse into it. Each file shows its size and modified date, you can download an individual file, and you can download the whole folder as a zip archive from the download control in the breadcrumb bar.

Browsing inside the documents folder, with release-notes.md and welcome.txt shown with their sizes and modified dates and a parent-directory link

Step 7: Search the listing

Type in the search box to filter the listing by file name. Directory Lister searches the served directory and shows the matching files with their paths.

Searching the listing for "example" and seeing data/example.csv and images/example.svg in the results

Step 8: Light and dark themes

Directory Lister ships light and dark themes; use the theme toggle in the top bar to switch. The listing renders the same content in either theme.

The same browsable directory listing rendered in the built-in dark theme

Step 9: Serve your own directory

To publish your own files, set FILES_PATH in the application environment file to your directory, then restart the web services:

# Point Directory Lister at your own directory of files:
#   1. Edit the environment file and set FILES_PATH to your directory:
sudo nano /var/www/dirlister/files/.dirlister-app/.env   # set FILES_PATH=/path/to/your/files
#   2. Restart the web services:
sudo systemctl restart php8.3-fpm nginx

Keep your served directory self-contained and free of sensitive files, exactly as the shipped demo directory is. The traversal and configuration-exposure protections shown in the next two steps apply to whatever directory you serve.

Step 10: Path traversal above the served root is refused

The served root is a dedicated directory, and any attempt to read a file above it — with relative paths, URL-encoded sequences or symlinks — returns 404. Nothing outside the served directory can be read:

# relative-path traversal
curl -s -o /dev/null -w '?dir=../../../../etc            -> HTTP %{http_code}\n' 'http://127.0.0.1/?dir=../../../../etc'
curl -s -o /dev/null -w '?file=../../../../etc/passwd    -> HTTP %{http_code}\n' 'http://127.0.0.1/?file=../../../../etc/passwd'
# URL-encoded traversal
curl -s -o /dev/null -w 'url-encoded traversal          -> HTTP %{http_code}\n' 'http://127.0.0.1/?file=..%2f..%2f..%2f..%2fetc%2fpasswd'
# and nothing from /etc/passwd is ever returned:
echo "leaked /etc/passwd lines: $(curl -s 'http://127.0.0.1/?file=../../../../etc/passwd' | grep -c 'root:.*:0:0' || true)"

The traversal requests each returning HTTP 404 and zero bytes of /etc/passwd leaked, while the demo listing itself renders

Step 11: The application's own config and source are never served

The application's environment file, configuration and source live in a hidden subdirectory inside the served root. They are excluded from the listing and return 404 over HTTP, while the compiled front-end assets are served normally:

curl -s -o /dev/null -w 'GET /.env                 -> HTTP %{http_code}\n' 'http://127.0.0.1/.env'
curl -s -o /dev/null -w 'GET /app/config/app.php   -> HTTP %{http_code}\n' 'http://127.0.0.1/app/config/app.php'
curl -s -o /dev/null -w '?file=.dirlister-app/.env -> HTTP %{http_code}\n' 'http://127.0.0.1/?file=.dirlister-app/.env'
# the app directory never appears in the rendered listing:
echo "app dir in listing: $(curl -s 'http://127.0.0.1/' | grep -c 'dirlister-app' || true)  (0 = hidden)"

The .env, app config and app source each returning HTTP 404 over the web and the app directory absent from the listing, while the compiled front-end JavaScript is served with a JavaScript MIME type

Step 12: How the first-boot gate works

nginx and php8.3-fpm are each gated on a bootstrap marker that the first-boot service writes only after it has prepared the instance. Until the marker exists systemd skips those units; the units are enabled so they survive a reboot:

grep -h ConditionPathExists /etc/systemd/system/nginx.service.d/*.conf /etc/systemd/system/php8.3-fpm.service.d/*.conf
sudo ls -l /var/lib/cloudimg/
sudo systemctl is-enabled php8.3-fpm nginx directory-lister-firstboot

The systemd drop-ins for nginx and php-fpm both showing ConditionPathExists on the cloudimg directory-lister bootstrap-ready marker, the marker and first-boot sentinel present in /var/lib/cloudimg, and all three units enabled so they survive a reboot

Step 13: Security recommendations

  • Keep the served directory self-contained. Serve a dedicated directory of the files you want to publish; do not point FILES_PATH at a home directory, a system path, or anywhere holding secrets. The traversal protection stops reads above the served root, but everything inside the served directory is, by design, browsable.
  • Enable password protection for a private listing. Directory Lister supports built-in password/hash directory protection; enable it if the listing should not be public. See the Directory Lister configuration documentation.
  • Put TLS in front. Serve over HTTPS with an Azure Application Gateway, a load balancer or a reverse proxy such as Caddy or nginx with a certificate.
  • Restrict inbound access. Use an Azure Network Security Group to limit who can reach ports 80 and 22.
  • Keep the OS patched. Unattended security upgrades are enabled; reboot periodically to apply kernel updates.

Step 14: Support and licensing

Directory Lister is open-source software distributed under the MIT License; the licence text ships in the image at /var/www/dirlister/files/.dirlister-app/LICENSE. This cloudimg image bundles Directory Lister with nginx and PHP on Ubuntu 24.04 and adds the security hardening, first-boot preparation and deployment tooling described above.

cloudimg provides 24/7 support for this image by email (support@cloudimg.co.uk) and live chat, covering deployment, pointing the listing at your own directory, sort and hidden-file rules, themes and titles, enabling password protection, zip and download behaviour, serving over HTTPS, and Directory Lister version upgrades. All product and company names are trademarks or registered trademarks of their respective holders.