Storage AWS

HFS HTTP File Server 3.3.4 on AWS User Guide

| Product: HFS HTTP File Server 3.3.4 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the HFS HTTP File Server 3.3.4 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.

HFS (HTTP File Server) shares files and folders from a web browser: browse, download, upload by drag and drop, download whole folders as ZIP archives, search, and reach the same files over WebDAV. Its admin panel manages accounts, virtual folders and per folder permissions (who can see, list, download, upload and delete).

Upstream HFS is made for a desktop: it starts with no account and lets anyone connecting from the same machine into the admin panel without a password, which behind a local reverse proxy means everyone. The cloudimg image does the opposite:

  • HTTPS from the first boot. nginx serves HFS on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • An admin password unique to your instance. The account is admin; its password is generated on first boot and written to /root/hfs-credentials.txt. HFS stores only an SRP verifier of it (the same scheme HFS uses itself); the plain password never reaches the configuration file. No password is baked into the image.
  • No anonymous access. Every file, listing, ZIP download and WebDAV request needs a login, the admin panel always asks for one (also from the instance itself), and only administrators can upload or delete until you grant more in the admin panel.
  • Closed until first boot, and fail closed. The image holds no HFS configuration, so HFS cannot start, and nginx ships switched off. First boot creates the password and the certificate, starts HFS, and only then opens the public listener; if anything in that sequence fails, the listener is closed again.
  • Hardened defaults. HFS listens on loopback 127.0.0.1:8080 behind nginx and runs as its own unprivileged account in a systemd sandbox that can write only to its state directory and the shared folder. HFS and nginx are both blocked from the instance metadata service; the AMI requires IMDSv2; requests for any host name the instance does not serve are dropped; nginx passes HFS the real client address and refuses state-changing requests that a web browser sends on behalf of another site; HFS's brute-force protection is on and UPnP is off.
  • Current, patched runtime. HFS 3.3.4 is upstream's security release. It runs on Node.js 24.22.1 (the upstream single-file binary bundles an older Node.js, so the image installs the same HFS release from the npm registry instead), with every dependency pinned by checksum.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: HFS 3.3.4 at /opt/hfs/app with Node.js 24.22.1 at /opt/hfs/node (both owned by root, so the service cannot change its own code), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The shared folder holds a short welcome note.

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 UI, admin panel and WebDAV) 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 HFS HTTP File Server 3.3.4 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (HFS needs little CPU; size for your users and transfer volume), your key pair, and a security group as above. Size the root EBS volume for the files you plan to store; the shared folder 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=hfs}]'

Step 2: Connect to your instance

Version SSH user
HFS HTTP File Server 3.3.4 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the password and the certificate, starts HFS, opens nginx and proves an authenticated round trip before it finishes (usually well under a minute):

systemctl is-active hfs nginx hfs-firstboot
sudo test -f /var/lib/cloudimg/hfs-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; HFS 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:(("HFS 3.3.4",...))
[::]:22 users:(("sshd",...))
[::]:443 users:(("nginx",...))
[::]:80 users:(("nginx",...))

Step 4: Get your URL and password

sudo cat /root/hfs-credentials.txt

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

HFS_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
HFS_ADMIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/~/admin/
HFS_ADMIN_USER=admin
HFS_ADMIN_PASSWORD=<HFS_ADMIN_PASSWORD>

The URL uses the instance's public DNS name from EC2 instance metadata (the public IP address when the subnet has no public DNS names). The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve HFS under your own DNS name, see Step 10.

Step 5: Sign in and share files

Open the HFS_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 10). Nothing is shown without a login; the login dialog opens straight away:

The HFS login dialog shown to a visitor who has not signed in

Sign in as admin with the password from Step 4. The shared folder /srv/hfs-files appears as files. Open it to see its contents; the folders below were added for the screenshots:

The files folder listing two folders and WELCOME.txt, with the Select, Upload, Search, Options and Zip buttons, signed in as admin

Click a folder to browse into it and a file to open or download it. Upload sends files (or drag them onto the page), Zip downloads the current folder as one archive, and Select lets you download, move or delete several entries at once:

The contents of the Project Halyard folder: a CSV file and a Markdown file with their sizes and dates

HFS also serves the same folders over WebDAV at the same URL, so Windows File Explorer (Map network drive), macOS Finder (Connect to Server), GNOME Files, Cyberduck, WinSCP and rclone can mount them with the same accounts. Clients that check certificates (Windows always does) need a certificate they trust: your own (Step 10), or this instance's certificate imported as trusted. Its fingerprint, to compare with what your client shows:

openssl x509 -noout -subject -fingerprint -sha256 -in /etc/ssl/hfs/hfs.crt

Step 6: The admin panel

Open the HFS_ADMIN_URL (/~/admin/) and sign in as admin. The panel manages everything HFS does; changes made here are saved by HFS in its own configuration file:

The HFS 3.3.4 admin panel home page: server is working, welcome admin, with the Home, Shared files, Accounts, Options, Internet, Monitoring, Logs, Language, Plugins and Custom HTML sections

Accounts adds people and groups. Give each person their own account; tick Admin access only for those who should manage the server:

The Accounts page listing the single admin account and the Add button

Shared files is the virtual file system: add more folders from the disk, rename them as people see them, and set who can see, list, download, upload and delete per folder. The image shares /srv/hfs-files as files, readable by every signed-in account, with upload and delete for administrators only:

The Shared files page showing the Home folder and the files folder in the virtual file system

HFS runs as the hfs account and may only write inside /var/lib/hfs (its state) and /srv/hfs-files. To share another directory, make it readable by hfs, and for uploads add it to the sandbox with sudo systemctl edit hfs ([Service] then ReadWritePaths=/var/lib/hfs /srv/hfs-files /your/dir) and restart HFS.

HFS 3.3.4 does not trust a proxied request from 127.0.0.1 with the admin panel or admin-only actions such as uploads. If you reach the instance through an SSH tunnel, forward to its private IP address (ssh -L 8443:<private-ip>:443 ...), not to 127.0.0.1.

Step 7: Use it from the command line

Port 80 redirects to HTTPS, a request without a login is refused, and the health check stays open for load balancers (it is answered by nginx and reveals nothing about your files):

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

With the password, upload a file, read it back, list the folder through the HFS API and delete the file again. 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/hfs-credentials.txt
  P=$(grep "^HFS_ADMIN_PASSWORD=" $F | cut -d= -f2-)
  H=$(grep "^HFS_URL=" $F | sed -E "s#^HFS_URL=https://([^/]+)/.*#\1#")
  IP=$(hostname -I | cut -d" " -f1)
  hfs() { printf "user = \"admin:%s\"\n" "$P" | curl -K - -s --cacert /etc/ssl/hfs/hfs.crt --connect-to "$H:443:$IP:443" "$@"; }
  echo "hello from cloudimg" > /run/report.txt
  hfs -o /dev/null -w "upload   -> HTTP %{http_code}\n" -T /run/report.txt "https://$H/files/report.txt"
  echo "download -> $(hfs "https://$H/files/report.txt")"
  hfs "https://$H/~/api/get_file_list?uri=/files/" | python3 -c "import json,sys; print(\"list     ->\", sorted(e[\"n\"] for e in json.load(sys.stdin)[\"list\"]))"
  hfs -o /dev/null -w "delete   -> HTTP %{http_code}\n" -X DELETE "https://$H/files/report.txt"
  rm -f /run/report.txt
'
upload   -> HTTP 200
download -> hello from cloudimg
list     -> ['WELCOME.txt', 'report.txt']
delete   -> HTTP 200

From your own machine, use the URL and password from Step 4. -u admin makes curl prompt for the password instead of taking it on the command line; -k accepts the self signed certificate:

curl -k -u admin -T ./photo.jpg https://<public-ip>/files/photo.jpg
curl -k -u admin -O https://<public-ip>/files/photo.jpg

Files uploaded through HFS are owned by the hfs account. You can also copy files straight into /srv/hfs-files over SSH; make them readable by that account (sudo chown -R hfs:hfs /srv/hfs-files).

Step 8: Run the built in check

The image ships an end to end check you can run at any time; first boot runs it too. It reads the credentials file itself and drives HFS over HTTPS through nginx, with the instance's certificate pinned:

sudo hfs-verify.sh
port 80 -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ ; an unknown Host is dropped on 80 and 443; /healthz answers ok
anonymous: files, listings, ZIP, WebDAV, admin APIs (also with a forged X-Forwarded-For 127.0.0.1), account creation, upload and delete all refused (401)
admin admin: admin API 200 (account listed with admin rights); the shared folder lists WELCOME.txt
upload/download byte-compare identical (262144 bytes, sha256 <sha256>); cross-origin upload 403; delete then 404
a forged X-Forwarded-For is not believed: HFS logs the socket peer
guessed credentials (admin/admin, admin/please-change, the right password for another user) refused (401)
HFS_ROUNDTRIP_OK https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ via <private-ip>: anonymous refused everywhere incl. the admin API; admin API 200 (account listed with admin rights); listing, upload, download+byte-compare and delete verified over HTTPS

It leaves the shared folder exactly as it found it. Because it tries wrong passwords on purpose, HFS's brute-force protection makes the next few logins from the instance wait a few seconds.

Step 9: Security checks

HFS's configuration holds the hardened settings and only an SRP verifier of the admin password (the command below hides the verifier), and the files that matter are locked down:

sudo sed -E 's/(srp: ).*/\1<salt>|<verifier>/' /var/lib/hfs/config.yaml | head -14
sudo stat -c '%a %U:%G %n' /var/lib/hfs/config.yaml /var/lib/hfs /srv/hfs-files /root/hfs-credentials.txt /etc/ssl/hfs/hfs.key
port: 8080
listen_interface: 127.0.0.1
force_https: false
proxies: 1
localhost_admin: false
base_url: https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com
open_browser_at_start: false
upnp_enabled: false
accounts:
  admin:
    srp: <salt>|<verifier>
    admin: true
vfs:
  can_see: "*"
600 hfs:hfs /var/lib/hfs/config.yaml
750 hfs:hfs /var/lib/hfs
750 hfs:hfs /srv/hfs-files
600 root:root /root/hfs-credentials.txt
600 root:root /etc/ssl/hfs/hfs.key

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

H=$(sudo grep '^HFS_URL=' /root/hfs-credentials.txt | sed -E 's#^HFS_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

HFS runs unprivileged in a sandbox that can write only to its state and the shared folder, and neither HFS nor nginx can reach the instance metadata service:

systemctl show hfs -p User -p ProtectSystem -p ReadWritePaths -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=hfs
ReadWritePaths=/var/lib/hfs /srv/hfs-files
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32

nginx passes HFS a fixed host name and the real client address from the connection, and blanks the forwarded host, prefix and port headers, so HFS (which trusts exactly one proxy) never sees values a client made up:

grep -E 'proxy_set_header' /etc/nginx/sites-available/cloudimg-hfs
        proxy_set_header Host $hfs_host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host "";
        proxy_set_header X-Forwarded-Port "";
        proxy_set_header X-Forwarded-Prefix "";
        proxy_set_header Forwarded "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Connection "";

No per instance password reached a log, the journal, shell history, backups, /etc, HFS's state or the shared folder:

sudo hfs-secret-leak-check.sh
LEAKCHECK_OK no per-instance secret in the journal, auth.log, syslog, cloud-init, nginx logs, shell history, MOTD, /var/backups, /etc, HFS's state and logs, /tmp or the share; config.yaml holds an SRP verifier only

nginx's access log (/var/log/nginx/access.log) records paths without their query strings, so an HFS ?login= link cannot write a password into it. HFS keeps its own access log in /var/lib/hfs/logs/.

Step 10: Your own DNS name and certificate

Point a DNS record at the instance, then set the name HFS is served as. This mints a new self signed certificate for the name, updates the nginx host and same-origin rules, HFS's base_url and the URLs in /root/hfs-credentials.txt, restarts HFS (everyone signs in again) and keeps the name across reboots:

sudo hfs-set-hostname <your-domain>

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

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

Behind an Application Load Balancer with a certificate from AWS Certificate Manager, register the instance on port 443 (HTTPS), use /healthz as the health check path, and set the instance's name to your domain with hfs-set-hostname; a request for a name the instance does not serve is dropped. If you put another proxy in front, keep it the only path to the instance: HFS trusts one proxy (nginx), and nginx records the load balancer's address as the client.

Step 11: Passwords and accounts

Change the admin password in the admin panel (Accounts, select admin, then change the password), which keeps it as an SRP verifier. For recovery or automation the image also ships a tool that prompts twice without echoing, writes a fresh SRP verifier and restarts HFS; the password never appears on a command line, in the logs or in your history:

sudo hfs-set-password admin

Then update or remove HFS_ADMIN_PASSWORD in /root/hfs-credentials.txt so it does not hold an old password. Create everyone else's accounts in the admin panel; an account without Admin access can browse and download the shared folder but not upload, delete or open the admin panel until you grant it in Shared files.

Step 12: Configuration

HFS keeps its configuration in /var/lib/hfs/config.yaml and saves it itself when you change something in the admin panel. The image sets:

Setting Meaning Image default
listen_interface / port Bind address 127.0.0.1 / 8080 (nginx fronts it)
localhost_admin Admin panel without a password from localhost false (always a login)
proxies Trusted proxies for client addresses 1 (nginx)
base_url Address used in links HFS generates https://<your host> (set by hfs-set-hostname)
force_https / https_port HFS's own HTTPS off (nginx terminates TLS)
upnp_enabled Router port mapping false
open_browser_at_start Open a browser on start false
vfs Shared folders and permissions /srv/hfs-files as files; signed-in accounts read, admins upload and delete
enable_plugins Plugins antibrute (brute-force delays)

HFS applies most changes as soon as the file is saved. To edit it by hand, stop HFS first (sudo systemctl stop hfs), edit, then start it again. nginx streams uploads with no size cap (client_max_body_size 0 in /etc/nginx/sites-available/cloudimg-hfs); set a value there and reload nginx if you want one.

Step 13: Server components

Component Version Path
HFS 3.3.4 /opt/hfs/app
Node.js 24.22.1 /opt/hfs/node/bin/node
nginx 1.24.0 /etc/nginx/sites-available/cloudimg-hfs
Ubuntu 24.04 LTS
Path Purpose
/srv/hfs-files The shared folder (0750 hfs:hfs)
/var/lib/hfs/config.yaml HFS configuration (accounts as SRP verifiers)
/var/lib/hfs/logs/ HFS access and error logs
/etc/ssl/hfs/ Certificate and key
/root/hfs-credentials.txt URLs, user and password (mode 0600)
/opt/hfs/SOURCE Where HFS and Node.js came from, with checksums
/usr/local/sbin/hfs-firstboot.sh First boot
/usr/local/sbin/hfs-set-hostname Host name and certificate
/usr/local/sbin/hfs-set-password Password recovery
/usr/local/sbin/hfs-verify.sh End to end check (Step 8)
/usr/local/sbin/hfs-secret-leak-check.sh Proves no password reached a log
/opt/hfs/node/bin/node -v
/opt/hfs/node/bin/node -p "require('/opt/hfs/app/package.json').version"
sudo journalctl -u hfs-firstboot --no-pager -o cat | grep -E 'Serving as|First boot complete'

The admin panel's Check for updates shows when a newer HFS is out, but HFS cannot update itself on this image (its code is owned by root). Take new versions from a new version of this listing, which also brings a current Node.js.

Step 14: Back up the shared folder

Archive the shared folder and HFS's configuration into a root only directory, and never copy the credentials file into a backup location:

sudo install -d -o root -g root -m 0700 /var/backups/hfs
sudo bash -c 'umask 077; tar -czf /var/backups/hfs/hfs-$(date -u +%Y%m%d).tar.gz -C / srv/hfs-files var/lib/hfs/config.yaml'
sudo ls /var/backups/hfs/
sudo hfs-secret-leak-check.sh

Copy the archive off the instance (for example to Amazon S3), or snapshot the root volume. The archive holds your accounts as SRP verifiers, so keep it private. To restore, stop HFS, extract the archive at /, run sudo chown -R hfs:hfs /srv/hfs-files /var/lib/hfs and start HFS.

Troubleshooting

  • The admin panel says Forbidden, or uploads are refused although you signed in as admin. You are coming through an SSH tunnel or a proxy to 127.0.0.1. HFS does not trust proxied loopback requests with admin rights; tunnel to the instance's private IP address instead (Step 6).
  • The browser or client closes the connection. nginx drops requests for host names it does not serve. Use the name in HFS_URL, the instance's IP addresses, or set your own name with hfs-set-hostname (Step 10).
  • Logins are slow after wrong passwords. HFS's antibrute plugin delays an address after failed logins (5 seconds more per failure, up to a minute). Wait, or adjust it under Plugins in the admin panel.
  • A new shared folder cannot be written to. HFS may only write inside /var/lib/hfs and /srv/hfs-files; add the directory to ReadWritePaths (Step 6) and make it writable by hfs.
  • Nothing answers after launch. Check systemctl status hfs-firstboot and journalctl -u hfs-firstboot. Until first boot completes, HFS is deliberately closed.

Security recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Keep the admin password secret, change it if it may have been shared, give each person their own account without admin access, and grant upload and delete only on the folders that need it.
  • Use your own DNS name and a certificate your clients trust (Step 10).
  • Prefer signing in over ?login= links: they carry a password in the address, which ends up in browser history.
  • Treat the shared folder as shared content: anyone who can upload can place files another user will open. nginx sends X-Content-Type-Options: nosniff, so browsers do not reinterpret an uploaded file.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.
  • Known advisories with no fixed release, not reachable in this configuration: node-forge 1.4.0 (GHSA-86w9-cpqp-85rv, signature verification; HFS only uses it to create a self signed certificate) and zod 3.25.76 (GHSA-mrhx-c7xc-m3w5; used only to check HFS's own session settings at start-up).
  • Back up the shared folder (Step 14).

Support and licensing

HFS is open source under the GNU General Public License v3 (the licence text is at /opt/hfs/app/LICENSE.txt; source: https://github.com/rejetto/hfs/tree/v3.3.4). Node.js is MIT licensed. There is no per user or per CPU licence fee; the AMI price covers cloudimg's hardening, first boot setup and support. HFS is a trademark of its respective owner; cloudimg is not affiliated with or endorsed by the HFS project or its author.

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