Putting the final pieces to the puzzle

📷Mor THIAM

I wanted a file share somewhere on the Internet and WebDAV is one of the most widely supported protocols. Before it, I considered the alternatives:

  • Give the files to a “cloud” provider to do with them as they please. No. Even if they are just groceries lists.
  • SSHFS works nicely without any effort, except that it’s slow and not supported by Windows Explorer.
  • Nextcloud would also work, but I think it’s an overkill since it’s a fairly complex PHP application that does many things, most of which I don’t care about, and yet it doesn’t do other things I care about, such as being compatible with rsync.

I wanted to do it my way and KISS. I finally managed to do it my way but KISS is in the eye of the beholder.

There are several guides to setup Nginx or Apache for file sharing with WebDAV. Except… for all the small bugs and incompatibilities, as I found out along the way. You see, I wanted to access the share from several clients…

  • Windows Explorer
  • KDE Dolphin
  • Gnome and MATE
  • Android FolderSync

And many of them had their own idiosyncratic way of interpreting the RFCs that describe WebDAV.

Here are some of the bugs, problems, things I couldn’t get the server and clients to agree on:

  • Sensitive to trailing slashes.
  • Windows Explorer sends an OPTIONS / even when the share is in a subfolder.
  • Must support LOCK.
  • MOVE and COPY expect a Destination: header to match the Host: scheme (which isn’t necessarily true if you use a reverse proxy).
  • KDE Dolphin couldn’t interprete the MIME type when used with a Go based client. This seems to have been fixed in the latest version of Dophin.

I had to try a few solutions until I found something that fully worked.

Nginx on its own

Nginx has rudimentary support for WebDAV and needs a separately maintained extension to support LOCK and other methods. It suffered from incompatibilities with missing trailing slashes. There are some guides that attempt to add all the extra hacks needed to make it work, but I gave up on this for the time being. If you are interested in trying it anyway, be informed that the version of the extension included in Ubuntu 20.04 LTS is not recent enough.

The next thing to try was to use a seperate WebDAV server and put it behind a Nginx reverse server, which is what this blog post is about. I already used Nginx for some web hosting and I simply wanted to add a WebDAV file share.

Dave

It’s a standalone WebDAV server with a simple .yaml configuration. It supports multiple users, each with their own folder and password protection. It is written in Go, using the same semi-standard library that Caddy uses for its WebDAV extension. What it doesn’t do is provide “autoindex”, to be able to access the files using a browser, but we can add this functionaly using our reverse proxy.

Do give it a try, if you just want something to quickly set up and be done with it. However I should let you know that its development has finished successfully, all planned features have been added and all bugs have been squashed, which means it won’t be updated further except to keep its dependencies up to date. Here is a fork I made with updated dependencies and minor cleanup.

Apache

Apache is probably the oldest and most widely used implementation of a WebDAV server, except for the fact that I already use Nginx so why would I want to install Apache as well? Just for its WebDAV functionality, behind Nginx.

I won’t be giving full installalation instructions here, there are enough guides for that. However just for completion and because everybody loves copy-paste here is an example of how it looks like:

DavLockDB /usr/local/apache/var/DavLock
<VirtualHost 127.0.0.1:8080>
    ServerAdmin admin@domain.tld
        ServerName domain.tld
        ServerAlias domain.tld
        DocumentRoot /path/to/webdav
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        #Alias /webdav /path/to/webdav

        <Directory /path/to/webdav>
            DAV On
        </Directory>

        # See https://github.com/BytemarkHosting/docker-webdav/blob/master/2.4/conf/conf-available/dav.conf
        # We turn it on for all User Agents here because Windows Explorer and KDE Dolphin appear affected
        SetEnv redirect-carefully
</VirtualHost>

If you use /srv take a look at apache2.conf to make sure the proper lines are uncommented. You also have to create /usr/local/apache/var/DavLock owned and writable by www-data:www-data (on Ubuntu/Debian).

Nginx Reverse Proxy

Here is the fun part! (not really).

After putting Dave or Apache behind the reverse proxy, it worked as intended except for renaming and copying files. Investigating further and standing on the shoulders of previous sufferers giants, I finally figured the cause: MOVE and COPY methods use a Destination header that must match the scheme of Host e.g. https for https and not http. This isn’t true behind a reverse proxy.

Add to this a long standing bug in Nginx and its suggested workaround and you get this Magic Copypasta:

    set $dest $http_destination;
    if ($http_destination ~ "^https://(?<myvar>(.+))") {
       set $dest http://$myvar;
    }
    proxy_set_header Destination $dest;

It looks ugly, but its a workaround for a bug after all.

Here’s how it ended up looking like after several iterations. Nginx handles the Basic Authentication. There are some optimizations to use keepalive and reduce the number of localhost connections that may or may not matter in practice.

upstream webdav {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    server_name domain.tld;

    root /var/www/html;

    access_log /var/log/nginx/domain.tld;

    client_max_body_size 0;

    location / {
        auth_basic           "Restricted area";
        auth_basic_user_file /etc/nginx/.htpasswd;

    # https://mailman.nginx.org/pipermail/nginx/2007-January/000504.html - fix Destination: header
    # https://trac.nginx.org/nginx/ticket/348 - bug, workaround with named capture
    set $dest $http_destination;
    if ($http_destination ~ "^https://(?<myvar>(.+))") {
       set $dest http://$myvar;
    }
    proxy_set_header Destination       $dest;

    #rewrite /webdav/(.*) /$1 break;
    proxy_pass http://webdav;

    proxy_buffering off;

    # Keep-alive
    proxy_http_version                 1.1;
    proxy_set_header Connection        "";

    # Proxy headers
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;

    # Proxy timeouts between successive read/write operations, not the whole request.
    proxy_connect_timeout              300s;
    proxy_send_timeout                 300s;
    proxy_read_timeout                 300s;

    listen [::]:443 ssl http2;
    listen 443 ssl http2;
    # ... SSL stuff ...
}

Left as an exercise for the reader:

  • Create the .htpasswd file (I used Apache’s htpasswd).
  • Use 308 redirects instead of 301 to redirect http to https (because 301 doesn’t support all the request methods of WebDAV).
  • If needed, include an extra location with “autoindex”.