Graham Eddy

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:

Fundamental technical issues addressed in this configuration:


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
#  This file should be included in each http server{} section that
#  is going to do WebDav.
#  From
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!";
graham:/etc/nginx sudo vi snippets/webdav.conf
/etc/nginx/snippets/webdav.conf new file
#  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.
# (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;
graham:/etc/nginx sudo vi conf.d/webdav.conf
/etc/nginx/conf.d/webdav.conf new file
#  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

graham:/etc/nginx sudo vi sites-available/
/etc/nginx/sites-available/ new file
# - virtual server

server {
    listen 80;
    listen [::]:80;
    root /srv/http/;
    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/;
graham:/etc/nginx sudo mkdir -p /srv/http/ graham:/etc/nginx cd /srv/http/ graham:/srv/http/ sudo mkdir html graham:/srv/http/ sudo chown root:www-data html graham:/srv/http/ sudo chmod 2755 html graham:/srv/http/ sudo mkdir html/.config html/downloads graham:/srv/http/ sudo vi html/.config/autoindex.css
/srv/http/ 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: " ";
  opacity: 0.75;
th { width: auto !important; }
td:nth-child(1) { }
td:nth-child(2) { width: 6em; }
td:nth-child(3) { width: 15em; }
graham:/srv/http/ sudo mkdir realm graham:/srv/http/ sudo chmod 755 realm graham:/srv/http/ sudo ls -alR | grep -v '\.$' | grep -v '^$' | grep -v '^total ' .: drwxr-sr-x 5 root www-data 4096 Aug 17 17:39 html drwxr-xr-x 2 root www-data 4096 Aug 27 22:36 realm ./html: drwxr-sr-x 2 root www-data 4096 Aug 27 18:46 .config drwxr-sr-x 2 root www-data 4096 Aug 27 18:46 downloads ./html/.config: -rw-r--r-- 1 root www-data 338 Aug 27 18:46 autoindex.css ./html/downloads: ./realm: graham:/srv/http/ cd /etc/nginx/sites-enabled graham:/etc/nginx/sites-enabled sudo ln -s ../sites-available/ . # note the trailing dot! graham:/etc/nginx/sites-enabled sudo systemctl reload nginx graham:/etc/nginx/sites-enabled sudo systemctl status nginx graham:/etc/nginx/sites-enabled sudo certbot -d --nginx

Add a share to virtual server

Add share shared to virtual server This is often referred to externally by some variation on syntax //

graham:/etc/nginx/sites-enabled sudo vi
/etc/nginx/sites-enabled/ insert text block before final }
    # share 'shared'
    location ~ ^/shared(?:/(.*))?$ {
        include snippets/webdav.conf;
        auth_basic "shared";
        auth_basic_user_file /srv/http/;
graham:~ cd /srv/http/ graham:/srv/http/ sudo mkdir html/shared graham:/srv/http/ sudo chmod 2770 html/shared graham:/srv/http/ sudo vi html/shared/README.txt
/srv/http/ new file
This is a share: play nicely.
graham:/srv/http/ sudo touch realm/shared # no users yet

Add a virtual user to share

Add virtual user fred.nerk to share //

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.

graham:~ echo "fred.nerk:$(openssl passwd -apr1)" | \ sudo tee -a /srv/http/ Password: fred.nerk's password