WebDAV Server 5.17.3
Overview
WebDAV Server (hacdias/webdav) shares a folder over WebDAV, the file protocol built into Windows File Explorer (Map network drive), macOS Finder (Connect to Server) and Linux file managers. It is also spoken by rclone, Cyberduck, WinSCP, cadaver and davfs2. Clients list, upload, download, rename, copy, move and delete files and folders. A web browser gets a read-only listing of the same share.
Upstream WebDAV Server is a single binary with a configuration file. It has no TLS certificate, no account and no sandbox of its own, and the SecureAnyCloud image adds them. This guide covers launching and using the WebDAV Server 5.17.3 on Ubuntu 24.04 AMI on Amazon EC2. It is one version of the SecureAnyCloud SFTP and file server listing on AWS Marketplace, which you pick as the software version when you subscribe and launch.
What you get:
- HTTPS from the first boot. nginx serves the share on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A password unique to your instance. The account is
admin. Its password is generated on first boot and written to/root/webdav-credentials.txt. The server stores only a bcrypt hash of it (cost 12), and the plain password never reaches the configuration file. No password is baked into the image. - No anonymous access. Every request (listings, downloads, uploads and every WebDAV method) needs a login, and a wrong user name or password is refused in the same way.
- A confined share. The server runs as its own unprivileged account inside an empty chroot that holds only its binary, its configuration (read only) and the shared folder. A symbolic link to a file elsewhere on the instance, or a
..in a path, cannot reach outside the share. - Closed until first boot, and fail closed. The image holds no account, so the server cannot start, and nginx ships switched off. First boot creates the password and the certificate, starts the server, proves an authenticated round trip, and only then opens the public listener. If anything in that sequence fails, the listener is closed again.
- Hardened defaults. The server listens on loopback
127.0.0.1:8080behind nginx, in a systemd sandbox with no capabilities that may talk to the loopback interface only. Both the server and nginx are blocked from the instance metadata service, and the AMI requires IMDSv2. - Request filtering in nginx. nginx drops requests for any host name that the instance does not serve and passes the server the real client address. It refuses state-changing requests that a web browser sends on behalf of another site. It serves files with a sandboxing Content Security Policy, so an uploaded web page cannot run script.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: WebDAV Server 5.17.3 at /usr/local/bin/webdav (the official release binary, checked against its published checksum), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The shared folder holds a short README.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
Before you launch the instance, make sure you have:
- 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 (WebDAV and the browser listing) and TCP 80 (redirects to HTTPS) from your own networks only.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick WebDAV Server 5.17.3 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2.
Choose m5.large, your key pair, and a security group as described above. The server needs little CPU, so size the instance for your users and transfer volume. The shared folder lives on the root volume, so size the root EBS volume for the files you plan to store.
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=webdav}]'Connect to your instance
Version SSH user
WebDAV Server 5.17.3 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot generates the password and the certificate, starts the server, opens nginx, and proves an authenticated round trip before it finishes. This usually takes well under a minute:
systemctl is-active webdav nginx webdav-firstboot
sudo test -f /var/lib/sac/data/webdav-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. The WebDAV server 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:(("webdav",...)) [::]:22 users:(("sshd",...)) [::]:443 users:(("nginx",...)) [::]:80 users:(("nginx",...))Get your URL and password
sudo cat /root/webdav-credentials.txtOnly root can read the file. It holds the URL, the user name and this instance's password:
# WebDAV Server 5.17.3 - generated on first boot by webdav-firstboot.service
# These credentials are unique to this instance; none was baked into the image. The server stores only a bcrypt
# hash of the password (/etc/webdav/config.yml). Keep this file secret. WEBDAV_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ WEBDAV_USER=admin WEBDAV_PASSWORD=<WEBDAV_PASSWORD> ...The URL uses the instance's public DNS name from EC2 instance metadata, or 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 the share under your own DNS name, see Step 10.
Open the share in a browser
Open the WEBDAV_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).
The server shows nothing without a login. It asks for a user name and password, and if you cancel you see no content.
Sign in as admin with the password from Step 4. The browser shows a read-only listing of the share. A new instance holds only README.txt.
Click a folder to browse into it, and click a file to open or download it.
A browser can only read. To upload, rename, move and delete files, connect a WebDAV client (Step 6).
Connect a WebDAV client
Every client uses the WEBDAV_URL, the user admin and the password from Step 4:
Client How to connect
Windows File Explorer This PC, Map network drive, folder https://<your-host>/
macOS Finder Go, Connect to Server, https://<your-host>/
GNOME Files, Dolphin Other Locations or the address bar: davs://<your-host>/
rclone rclone config, storage type webdav, vendor other, URL https://<your-host>/
cadaver cadaver https://<your-host>/
davfs2 sudo mount -t davfs https://<your-host>/ /mnt/webdav
Cyberduck, WinSCP protocol WebDAV (HTTPS), port 443
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. To compare with what your client shows, print the certificate fingerprint:
openssl x509 -noout -subject -fingerprint -sha256 -in /etc/ssl/webdav/webdav.crtThe Windows WebDAV client also limits downloads to 50 MB by default (the FileSizeLimitInBytes value of the WebClient service). To move larger files with Explorer, raise the limit in the registry. Alternatively, use rclone, WinSCP or Cyberduck.
Use it 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 '^WEBDAV_URL=' /root/webdav-credentials.txt | sed -E 's#^WEBDAV_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http:// -> HTTP %{http_code}\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 'PROPFIND -> HTTP %{http_code}\n' -X PROPFIND --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:// -> HTTP 301 no login -> HTTP 401 PROPFIND -> HTTP 401 healthz -> HTTP 200With the password, upload a file, list the share with a WebDAV PROPFIND, read the file back, and delete it 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/webdav-credentials.txt P=$(grep "^WEBDAV_PASSWORD=" $F | cut -d= -f2-) H=$(grep "^WEBDAV_URL=" $F | sed -E "s#^WEBDAV_URL=https://([^/]+)/.*#\1#") IP=$(hostname -I | cut -d" " -f1) dav() { printf "user = \"admin:%s\"\n" "$P" | curl -K - -s --cacert /etc/ssl/webdav/webdav.crt --connect-to "$H:443:$IP:443" "$@"; } echo "hello from sac" > /run/report.txt dav -o /dev/null -w "upload -> HTTP %{http_code}\n" -T /run/report.txt "https://$H/report.txt" echo "list -> $(dav -X PROPFIND -H "Depth: 1" "https://$H/" | grep -o "<D:displayname>[^<]*" | cut -d">" -f2 | grep . | sort | tr "\n" " ")" echo "download -> $(dav "https://$H/report.txt")" dav -o /dev/null -w "delete -> HTTP %{http_code}\n" -X DELETE "https://$H/report.txt" rm -f /run/report.txt 'upload -> HTTP 201 list -> README.txt report.txt download -> hello from sac delete -> HTTP 204From your own machine, use the URL and password from Step 4. The -u admin option makes curl prompt for the password instead of taking it on the command line, and -k accepts the self-signed certificate:
curl -k -u admin -T ./photo.jpg https://<public-ip>/photo.jpg
curl -k -u admin -O https://<public-ip>/photo.jpg
curl -k -u admin -X MKCOL https://<public-ip>/Projects/Files uploaded over WebDAV are owned by the webdav account. You can also copy files straight into /srv/webdav over SSH. Make them readable by that account with sudo chown -R webdav:webdav /srv/webdav.
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 the server over HTTPS through nginx, with the instance's certificate pinned:
sudo webdav-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: GET, HEAD, PROPFIND, OPTIONS, PUT, DELETE, MKCOL, PROPPATCH, LOCK, COPY, MOVE all refused (401), also with a forged X-Forwarded-For IP admin: PROPFIND 207 lists README.txt; the browser listing renders PUT/GET byte-compare identical (262144 bytes, sha256 <sha256>); MKCOL, COPY (https://<host> Destination) and MOVE (https://<ip> Destination) 201; cross-origin PUT 403; files carry a sandbox CSP; DELETE then 404 confined: symlinks to /etc/passwd, the credentials file and /etc are not served; '..' and encoded '..' never leave the share guessed credentials (admin/admin, admin/webdav, webdav/webdav, the right password for another user, a near miss) refused (401) WEBDAV_ROUNDTRIP_OK https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ via <private-ip>: anonymous refused for every method; PROPFIND, listing, upload, download+byte-compare, MKCOL, COPY, MOVE and delete verified over HTTPS; symlinks and '..' confined to the shareIt leaves the shared folder exactly as it found it.
Security checks
The configuration holds only a bcrypt hash of the password (the command below hides it), and the files that matter are locked down. Check the permissions:
sudo sed -E 's/(\{bcrypt\}).*/\1<hash>"/' /etc/webdav/config.yml | grep -vE '^\s*#' | sed -n '/^users:/,$p'
sudo stat -c '%a %U:%G %n' /etc/webdav/config.yml /etc/webdav /srv/webdav /root/webdav-credentials.txt /etc/ssl/webdav/webdav.keyusers: - username: admin password: "{bcrypt}<hash>" permissions: CRUD 640 root:webdav /etc/webdav/config.yml 750 root:webdav /etc/webdav 750 webdav:webdav /srv/webdav 600 root:root /root/webdav-credentials.txt 600 root:root /etc/ssl/webdav/webdav.keyThe certificate was generated on this instance and names its own addresses:
H=$(sudo grep '^WEBDAV_URL=' /root/webdav-credentials.txt | sed -E 's#^WEBDAV_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:localhostThe server runs unprivileged with no capabilities, is chrooted into /var/lib/webdav/root, and may talk only to the loopback interface. Neither the server nor nginx can reach the instance metadata service:
systemctl show webdav -p User -p RootDirectory -p ProtectSystem -p NoNewPrivileges -p CapabilityBoundingSet -p IPAddressAllow -p IPAddressDeny
systemctl show nginx -p IPAddressDenyIPAddressAllow=::1/128 127.0.0.0/8 IPAddressDeny=::/0 0.0.0.0/0 RootDirectory=/var/lib/webdav/root CapabilityBoundingSet= User=webdav ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32Inside the chroot, the server sees its own binary, its configuration (mounted read only) and the share at /data, and nothing else from the instance. For this reason, a symbolic link in the share that points at /etc/passwd or the credentials file serves nothing:
sudo ls /var/lib/webdav/root systemd-analyze security webdav.service | tail -1config.yml data dev etc proc root run sys usr var webdav → Overall exposure level for webdav.service: 1.4 OK :-)The etc, root, run, usr and var directories are empty mount points that systemd uses for its sandbox.
The 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 '^WEBDAV_URL=' /root/webdav-credentials.txt | sed -E 's#^WEBDAV_URL=https://([^/]+)/.*#\1#')
N=$(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 -ic '^x-inj' || true)
echo "injected headers: $N"injected headers: 0nginx passes the server a fixed host name and the real client address from the connection. It blanks the forwarded host, prefix and port headers, and it reduces a WebDAV Destination header to its path. The server therefore never sees values that a client made up, and a COPY or MOVE always stays inside the share:
grep -E 'proxy_set_header' /etc/nginx/sites-available/sac-webdavproxy_set_header Host $webdav_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 Destination $sac_webdav_dest; proxy_set_header Connection "";Confirm that no per-instance password reached a log, the journal, shell history, backups, /etc or the shared folder:
sudo webdav-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, the share, /tmp; config.yml holds bcrypt hashes onlyThe server logs every failed login to its journal, with the client address and the user name tried (never the password). To list the failures, run sudo journalctl -u webdav | grep -E 'invalid (password|username)'.
Your own DNS name and certificate
Point a DNS record at the instance, then set the name that the share is served as. The command mints a new self-signed certificate for the name, updates the nginx host and same-origin rules and the URL in /root/webdav-credentials.txt, reloads nginx, and keeps the name across reboots:
sudo webdav-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running webdav-set-hostname again replaces it with a self-signed certificate:
sudo install -m 0644 <your-domain>.crt /etc/ssl/webdav/webdav.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/webdav/webdav.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 webdav-set-hostname, because the instance drops a request for a name it does not serve.
If you put another proxy in front, keep it the only path to the instance. nginx records the load balancer's address as the client.
Passwords and accounts
Change the password with the tool that the image ships. The tool prompts twice without echoing, or reads one line from standard input. It requires at least 12 characters, writes a fresh bcrypt hash into /etc/webdav/config.yml and restarts the server. The password never appears on a command line, in the logs or in your history:
sudo webdav-set-passwordThen update or remove WEBDAV_PASSWORD in /root/webdav-credentials.txt, so that the file does not hold an old password. Connected clients must sign in again with the new password.
webdav-set-password <user> renames the single account to <user>.
For several accounts, edit /etc/webdav/config.yml by hand (Step 12). Add an entry per person under users:, with a hash made by htpasswd -nBC 12 <user>. The command prompts for the password. Copy the part after the colon into password: "{bcrypt}...". If the accounts should not all share the same folder, give each account its own directory: or rules:.
Running webdav-set-password later rewrites the file with a single account.
Configuration
The server reads /etc/webdav/config.yml, which is owned by root and readable by the webdav group. Paths in the file are inside the chroot, where /data is /srv/webdav. The image sets:
Setting Meaning Image default
address / port Bind address 127.0.0.1 / 8080 (nginx fronts it)
tls The server's own TLS false (nginx terminates TLS)
behindProxy Take the client address from nginx true
directory The shared folder /data (/srv/webdav on the instance)
permissions Rights of a request without an account none
users Accounts, bcrypt hashes and their rights admin with CRUD (create, read, update, delete)
noSniff Content types from the file name only true
listing The browser listing on
cors Cross-origin requests from web pages off
log Log format and output console lines to the journal
After you edit the file, restart the server and run the built-in check:
sudo systemctl restart webdav
sudo webdav-verify.shThe server can see nothing outside its chroot, so keep shared files in /srv/webdav.
To hold the share on a separate EBS volume, stop the server and mount the volume at /srv/webdav. Add the mount to /etc/fstab, make the directory 0750 webdav:webdav, copy the README back if you want it, and start the server again.
nginx streams uploads with no size cap (client_max_body_size 0 in /etc/nginx/sites-available/sac-webdav). To set a cap, change the value there and reload nginx.
Server components
Component Version Path
WebDAV Server (hacdias/webdav) 5.17.3 /usr/local/bin/webdav
nginx 1.24.0 /etc/nginx/sites-available/sac-webdav
Ubuntu 24.04 LTS
Path Purpose
/srv/webdav The shared folder (0750 webdav:webdav)
/etc/webdav/config.yml Server configuration (accounts as bcrypt hashes)
/var/lib/webdav/root The empty chroot the server runs in
/etc/ssl/webdav/ Certificate and key
/root/webdav-credentials.txt URL, user and password (mode 0600)
/usr/share/doc/webdav/SOURCE Where the binary came from, with its checksum
/usr/local/sbin/webdav-firstboot.sh First boot
/usr/local/sbin/webdav-set-hostname Host name and certificate
/usr/local/sbin/webdav-set-password Password change
/usr/local/sbin/webdav-verify.sh End to end check (Step 8)
/usr/local/sbin/webdav-secret-leak-check.sh Proves no password reached a log
webdav version cat /usr/share/doc/webdav/SOURCE
sudo journalctl -u webdav-firstboot --no-pager -o cat | grep -E 'Serving as|First boot complete'New versions of the server arrive as new versions of this listing.
Back up the shared folder
Archive the shared folder and the 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/webdav
sudo bash -c 'umask 077; tar -czf /var/backups/webdav/webdav-$(date -u +%Y%m%d).tar.gz -C / srv/webdav etc/webdav/config.yml'
sudo ls /var/backups/webdav/
sudo webdav-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 bcrypt hashes, so keep it private.
To restore, stop the server and extract the archive at /. Then run sudo chown -R webdav:webdav /srv/webdav and start the server.
Troubleshooting
- The browser or client closes the connection. nginx drops requests for host names it does not serve. Use the name in
WEBDAV_URLor the instance's IP addresses, or set your own name withwebdav-set-hostname(Step 10). - Windows cannot map the drive. The Windows WebDAV client connects only over HTTPS to a certificate it trusts, and it needs the WebClient service running. Install your own certificate (Step 10), or import this instance's certificate into Trusted Root Certification Authorities.
- Uploads from a web page are refused (403). nginx refuses state-changing requests that a browser sends on behalf of another site. WebDAV clients are not affected.
- A file opened in the browser does not run its scripts. Files are served with a sandboxing Content Security Policy on purpose. Download the file instead.
- Nothing answers after launch. Check
systemctl status webdav-firstbootandjournalctl -u webdav-firstboot. Until first boot completes, the share is deliberately closed.
Security recommendations
- Allow ports 22, 80 and 443 in the security group from your own networks only.
- Keep the password secret, change it if it may have been shared (Step 11), and give each person their own account.
- Use your own DNS name and a certificate that your clients trust (Step 10).
- 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: nosniffand a sandboxing Content Security Policy, so browsers do not reinterpret or run an uploaded file. - Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. - Back up the shared folder (Step 14).
Support and licensing
WebDAV Server (hacdias/webdav) is open source under the MIT License. The license text is at /usr/share/doc/webdav/LICENSE, and the source is at https://github.com/hacdias/webdav/tree/v5.17.3. There is no per-user or per-CPU license fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
hacdias/webdav is the work of its respective authors. SecureAnyCloud is not affiliated with or endorsed by the 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 WebDAV Server 5.17.3 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