nginx wevDAV Server
Install and configure wevDAV modules of nginx for a domain with virtual users. Multiple domains each have their own wevDAV server.
2022-08-06 Raspbian bullseye, Ubuntu 22.04 (not 20.04)
This provides a password-protected wevDAV file server with two kinds of user access:
- Browser-based: a read-only view, navigating down the file tree. This view will see a top-level directory of all the shares, each of which can be navigated.
- wevDAV client: read-write access via an application, typically attached into macOS Finder or Windows File Explorer. Each attachment sees only the content of the relevant share.
Fundamental technical issues addressed in this configuration:
-
The most common wevDAV clients are not strictly standards-conformant
(the
trailing slash
deviation) but nginx enforces the standard → a significant workaround is required to make the clients work on the server. - The more recent common wevDAV clients require webDAV level 2 locking otherwise they allow only read-only mode. However, nginx has been tardy providing this locking; it is only available in recent versions and Debian packaging requires the variant with all options built in. In Ubuntu, a supporting version only became available on 22.04 release.
Installation
graham:~ sudo apt update graham:~ sudo apt install nginx-full libnginx-mod-http-cache-purge \ libnginx-mod-http-dav-ext libnginx-mod-http-fancyindex \ libnginx-mod-http-headers-more-filter libnginx-mod-nchan graham:~ cd /etc/nginx graham:/etc/nginx sudo vi snippets/webdav_server.conf/etc/nginx/snippets/webdav_server.conf
new file
#https://github.com/mcnewton/nginx_webdav/blob/master/snippets/webdav_server.conf # # This file should be included in each http server{} section that # is going to do WebDav. # # From https://gist.github.com/jirutka/5380770 # location ~ \.(_.*|DS_Store|Spotlight-V100|TemporaryItems|Trashes|hidden)$ { access_log off; error_log off; if ($request_method = PUT) { return 403; } return 404; } location ~ \.metadata_never_index$ { return 200 "Don't index this drive, Finder!"; }
/etc/nginx/snippets/webdav.conf
new file
#https://github.com/mcnewton/nginx_webdav/blob/master/snippets/webdav.conf # # General settings for WebDAV config with fixups for different # broken clients # # From what I can tell, all of the mainstream O/S based WebDav clients # are broken and don't follow the spec in subtly different ways. # # This file should be included in a location{} block that needs # WebDav access. # # # MacOS Sends MKCOL without the trailing slash # MOVE with trailing slash on URI but no trailing slash on destination # # Linux sends MKCOL with trailing slash # but MOVE with no trailing slash on URI or destination # # Windows sends MOVE with no trailing slash on URI or destination # # # Add / to URI with MKCOL, if needed # if ($request_method = MKCOL) { rewrite ^(.*[^/])$ $1/; } # # Handle missing trailing slash with MOVE and DELETE # # Can't do nested or complex if statements in nginx, so use $s # (source) and $t (target) to track the state. # set $s A; set $t A; # Check if it's a MOVE or DELETE operation if ($request_method = MOVE) { set $s B$s; set $t B$t; } if ($request_method = DELETE) { set $s B$s; } # Update URI if trailing slash is missing for MOVE or DELETE if (-d $request_filename) { set $s C$s; } if ($s = "CBA") { rewrite ^(.*[^/])$ $1/; } # More checks for Destination - first make sure the source is a # directory... if ($uri ~ ^.*/$) { set $t C$t; } # ...and destination doesn't already end in slash... if ($http_destination !~ ^.*/$) { set $t D$t; } # ...then fix up the destination by adding a slash on the end.if ($t = "DCBA") { set $destination $http_destination; set $destination $destination/; more_set_input_headers "Destination: $destination"; } # # Fix up for Windows, which sends PROPPATCH: return a fake "OK" to # keep it happy. # # https://github.com/arut/nginx-dav-ext-module/issues/52 # http://netlab.dhis.org/wiki/ru:software:nginx:webdav (Russian) # if ($request_method = PROPPATCH) { add_header Content-Type 'text/xml'; return 207 '<?xml version="1.0"?><a:multistatus xmlns:a="DAV:"><a:response><a:propstat><a:status>HTTP/1.1 200 OK</a:status></a:propstat></a:response></a:multistatus>'; } # # General "enable WebDav" stuff # dav_methods PUT DELETE MKCOL COPY MOVE; dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK; client_body_temp_path /tmp/nginx; client_max_body_size 0; create_full_put_path on; # Defined in conf.d/webdav.conf dav_ext_lock zone=xfer_webdav; dav_access user:rw group:rw;
/etc/nginx/conf.d/webdav.conf
new file
#https://github.com/mcnewton/nginx_webdav/blob/master/conf.d/webdav.conf # # On Debian based systems this file will be included in # httpd.conf http{} section automatically. Otherwise that's where # this config needs to go... # # See snippets/webdav.conf for where this is used. # dav_ext_lock_zone zone=xfer_webdav:10m;
Add a virtual server
Add virtual server docs.geddy.au.
graham:/etc/nginx sudo vi sites-available/docs.geddy.au/etc/nginx/sites-available/docs.geddy.au
new file
# docs.geddy.au - virtual server server { listen 80; listen [::]:80; server_name docs.geddy.au; root /srv/http/docs.geddy.au/html; index index.html index.htm; include snippets/webdav_server.conf; fancyindex on; fancyindex_exact_size off; fancyindex_localtime on; fancyindex_css_href /.config/autoindex.css; fancyindex_time_format "%a %d/%m/%Y at %I:%M %p"; location / { try_files $uri $uri/ =404; } # example share 'shared' #location ~ ^/shared(?:/(.*))?$ { # include snippets/webdav.conf; # # auth_basic "shared"; # auth_basic_user_file /srv/http/docs.geddy.au/realm/shared; #} }
/srv/http/docs.geddy.au/html/.config/autoindex.css
new file
/* autoindex.css - style for nginx autoindex pages */ h1 { margin: 0 0 0.4em 0; padding: 0.4em; font-size: 1.2em; background: indigo; color: white; } h1::before { content: "docs.geddy.au: "; opacity: 0.75; } th { width: auto !important; } td:nth-child(1) { } td:nth-child(2) { width: 6em; } td:nth-child(3) { width: 15em; }
- Verify created file tree: names, ownerships and permissions.
- Verify systemd thinks nginx has started successfully.
-
Browse to
http://docs.geddy.au
, overriding any browser concerns about navigating to a http (not https) site (this will be fixed later).- Verify website shows
Index of /
; header. - Verify
.config
is not displayed. - Verify
downloads
is displayed. - Click on
downloads
and observe empry directory.
- Verify website shows
- Verify LetsEncrypt think they have installed a new certificate.
- Select LetsEncrypt's option to permanently route traffic to ssl.
-
Browse to
https://docs.geddy.au
to verify ssl access.
Add a share to virtual server
Add share shared to virtual server docs.geddy.au.
This is often referred to externally by some variation on syntax
//docs.geddy.au/shared
.
/etc/nginx/sites-enabled/docs.geddy.au
insert text block before final }
# share 'shared' location ~ ^/shared(?:/(.*))?$ { include snippets/webdav.conf; auth_basic "shared"; auth_basic_user_file /srv/http/docs.geddy.au/realm/shared; }
/srv/http/docs.geddy.au/html/shared/README.txt
new file
This is a share: play nicely.
- Browse to
docs.geddy.au
. -
Verify that clicking on
shared
causes a login popup. We have no users yet so cannot login.
Add a virtual user to share
Add virtual user fred.nerk to share
//docs.geddy.au/shared
.
This is done at per-share level (as opposed to per-domain level)
to assign individual access rights to each share,
at the cost of duplicating user/password declarations.
The alternative is to have a single realm for all shares, or to symlink
all realms to a master
copy.
- Browse to
https://docs.geddy.au
. - Verify that clicking on shared causes a login popup.
-
This time, enter username
fred.nerk
and the password, and verify that access is gained and theREADME.txt
file is selectable. - See webDAV Clients for more comprehensive user access testing.