HFS HTTP File Server 3.4.0
Overview
HFS (HTTP File Server) shares files and folders from a web browser. You can browse, download, upload by drag and drop, download whole folders as ZIP archives, search, and reach the same files over WebDAV. The admin panel manages accounts, virtual folders and per-folder permissions: who can see, list, download, upload and delete.
This SecureAnyCloud image runs HFS HTTP File Server 3.4.0 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud SFTP and file server listing on AWS Marketplace. You choose it as the software version when you subscribe and launch.
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 SecureAnyCloud image does the opposite.
What you get:
- 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, so 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, even from the instance itself. 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:8080behind nginx. It 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, and 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.4.0 is upstream's newest release and includes every fix from the 3.3.4 security release. It runs on Node.js 24.22.1, with every dependency pinned by checksum. The upstream single-file binary bundles an older Node.js, so the image installs the same HFS release from the npm registry instead.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: HFS 3.4.0 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 that you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (the web UI, admin panel and WebDAV) and TCP 80 (redirect to HTTPS) from your own networks only.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick HFS HTTP File Server 3.4.0 on Ubuntu 24.04 as the software version, choose your Region, and select Launch through EC2.
Choose m5.large, your key pair, and a security group as described above. HFS needs little CPU, so size the instance for your users and transfer volume. Size the root EBS volume for the files you plan to store, because the shared folder lives on the root volume.
To launch with the AWS CLI instead, 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}]'Connect to your instance
The SSH user for HFS HTTP File Server 3.4.0 on Ubuntu 24.04 is ubuntu.
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot generates the password and the certificate, starts HFS, opens nginx, and proves an authenticated round trip before it finishes. This usually takes well under a minute:
systemctl is-active hfs nginx hfs-firstboot
sudo test -f /var/lib/sac/data/hfs-firstboot.done && echo FIRSTBOOT_DONEAll three units are active and the first-boot marker is present:
active active active FIRSTBOOT_DONEOnly 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' | sort0.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.4.0",...)) [::]:22 users:(("sshd",...)) [::]:443 users:(("nginx",...)) [::]:80 users:(("nginx",...))Get your URL and password
Read the credentials file:
sudo cat /root/hfs-credentials.txtOnly root can read the file. It 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. If the subnet has no public DNS names, it uses the public IP address.
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.
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. Alternatively, install your own certificate (Step 10). Nothing is shown without a login, so the login dialog opens straight away:
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:
Click a folder to browse into it, and click a file to open or download it. Upload sends files (or drag them onto the page). Zip downloads the current folder as one archive. Select lets you download, move or delete several entries at once:
HFS also serves the same folders over WebDAV at the same URL. 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. Use your own (Step 10), or import this instance's certificate as trusted. Compare its fingerprint with what your client shows:
openssl x509 -noout -subject -fingerprint -sha256 -in /etc/ssl/hfs/hfs.crtUse the admin panel
Open the HFS_ADMIN_URL (/~/admin/) and sign in as admin. The panel manages everything HFS does. HFS saves the changes you make here in its own configuration file.
Accounts adds people and groups. Give each person their own account, and tick Admin access only for those who should manage the server:
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:
HFS runs as the hfs account and may write only inside /var/lib/hfs (its state) and /srv/hfs-files. To share another directory, make it readable by hfs. For uploads, also 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 and later) does not trust a proxied request from 127.0.0.1 with the admin panel or with 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.
Use HFS from the command line
Port 80 redirects to HTTPS, and a request without a login is refused. The health check stays open for load balancers. nginx answers it, and it 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 200With 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 sac" > /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 sac list -> ['WELCOME.txt', 'report.txt'] delete -> HTTP 200From 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.jpgFiles 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 with sudo chown -R hfs:hfs /srv/hfs-files.
Run the built-in check
The image ships an end-to-end check that you can run at any time. First boot runs it too. The check reads the credentials file itself and drives HFS over HTTPS through nginx, with the instance's certificate pinned:
sudo hfs-verify.shport 80 -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ ; an unknown Host is dropped on 80 and 443; /healthz answers ok an encoded CR/LF in the path stays encoded in the redirect (no injected header on 80 or 443) 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 HTTPSThe check leaves the shared folder exactly as it found it. It tries wrong passwords on purpose, so HFS's brute-force protection makes the next few logins from the instance wait a few seconds.
Security checks
HFS's configuration holds the hardened settings and only an SRP verifier of the admin password. The command below hides the verifier. 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.keyport: 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.keyThe 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 subjectAltNamesubject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac 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:localhostHFS runs unprivileged in a sandbox that can write only to its state and the shared folder. 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 IPAddressDenyIPAddressDeny=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/32The port 80 redirect copies the request path exactly as it was sent, still percent-encoded. An encoded line break in a URL therefore cannot add a header of its own to the response:
H=$(sudo grep '^HFS_URL=' /root/hfs-credentials.txt | sed -E 's#^HFS_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -D - --connect-to "$H:80:127.0.0.1:80" "http://$H/x%0d%0aX-Inj:%201" | tr -d '\r' | grep -iE '^(HTTP/|location:|x-inj)' | sed "s#$H#<host>#"HTTP/1.1 301 Moved Permanently Location: https://<host>/x%0d%0aX-Inj:%201nginx passes HFS a fixed host name and the real client address from the connection. It blanks the forwarded host, prefix and port headers, so HFS (which trusts exactly one proxy) never sees values that a client made up:
grep -E 'proxy_set_header' /etc/nginx/sites-available/sac-hfsproxy_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.shLEAKCHECK_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 onlynginx'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/.
Use your own DNS name and certificate
Point a DNS record at the instance, then set the name that HFS is served as. The command below does the following:
- Creates a new self-signed certificate for the name.
- Updates the nginx host and same-origin rules, HFS's
base_urland the URLs in/root/hfs-credentials.txt. - Restarts HFS, so everyone signs in again.
- 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 nginxTo run behind an Application Load Balancer with a certificate from AWS Certificate Manager, register the instance on port 443 (HTTPS) and use /healthz as the health check path. Set the instance's name to your domain with hfs-set-hostname, because 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.
Passwords and accounts
Change the admin password in the admin panel: open Accounts, select admin, then change the password. HFS 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 adminThen update or remove HFS_ADMIN_PASSWORD in /root/hfs-credentials.txt, so that 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. It cannot upload, delete or open the admin panel until you grant that in Shared files.
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 the following (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 the file by hand, stop HFS first with sudo systemctl stop hfs, edit the file, then start HFS again.
nginx streams uploads with no size cap (client_max_body_size 0 in /etc/nginx/sites-available/sac-hfs). To set a limit, change the value there and reload nginx.
Server components
Software (component, version, path):
- HFS: 3.4.0, /opt/hfs/app
- Node.js: 24.22.1, /opt/hfs/node/bin/node
- nginx: 1.24.0, /etc/nginx/sites-available/sac-hfs
- Ubuntu: 24.04 LTS
Paths (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 that 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, because its code is owned by root. Take new versions from a new version of this listing, which also brings a current Node.js.
Back up the shared folder
Archive the shared folder and HFS's configuration into a root-only directory. 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.shCopy 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 and extract the archive at /. Then 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_URLor the instance's IP addresses, or set your own name withhfs-set-hostname(Step 10). - Logins are slow after wrong passwords. HFS's antibrute plugin delays an address after failed logins, by 5 seconds more per failure, up to a minute. Wait, or adjust the plugin under Plugins in the admin panel.
- A new shared folder cannot be written to. HFS may write only inside
/var/lib/hfsand/srv/hfs-files. Add the directory toReadWritePaths(Step 6) and make it writable byhfs. - Nothing answers after launch. Check
systemctl status hfs-firstbootandjournalctl -u hfs-firstboot. HFS is deliberately closed until first boot completes.
Security notes
- Allow ports 22, 80 and 443 in the security group from your own networks only.
- Keep the admin password secret, and 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 them.
- Use your own DNS name and a certificate your clients trust (Step 10).
- Sign in through the login dialog rather than with
?login=links. Those links 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 that 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 upgradeapplies everything else. - The HFS dependency set is checked with
npm auditat build time, and the build fails on any known advisory. - 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, and the source is at https://github.com/rejetto/hfs/tree/v3.4.0. Node.js is MIT licensed.
There is no per-user or per-CPU licence fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
HFS is a trademark of its respective owner. SecureAnyCloud is not affiliated with or endorsed by the HFS project or its author.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24-hour response SLA
Deploy on AWS
Launch HFS HTTP File Server 3.4.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com