---
title: "WebDAV with nginx"
date: 2016-03-26T00:00:00+01:00
draft: false
share: false
---

This website has been hosted on an [Online.net](https://www.online.net) dedicated server since its creation. I've been one of their customers for the past 3 years now, and I still don't have anything bad to say about them.

They recently upgraded their personnal range, and I took the opportunity to upgrade from a single server running all of my services to 2 servers running LXC containers that are hosting my services.

It took me 2 days to migrate everything, but it was worth it. If I decide to switch servers again, I'll have to migrate the containers instead of the services themselves. Considering they are stored on a separate BTRFS volume, it shouldn't take me more than a few hours at most.

During the migration, I realized that I needed to make files that were hosted on one server accessible to the other. I could have gone with CIFS or NFS, but I wanted to have encryption built-in instead of having to rely on a VPN for that. Since I figured it was a good opportunity to learn something new, I ended up going with WebDAV.

In this tutorial, I'll explain how I've configured a read-only WebDAV share using [nginx](https://www.nginx.com/) and [Let'sEncrypt](https://letsencrypt.org/) SSL certificates between two Debian Jessie containers.

## Server configuration

### Installing the required packages

First thing first, we need to install the packages we'll need for this configuration :

```bash
apt update

apt -t jessie-backports install nginx letsencrypt
apt install apache2-utils
```

### Getting our first certificate from letsencrypt

#### letsencrypt configuration

Let's create a configuration file for letsencrypt :

```bash
mkdir /etc/letsencrypt

echo 'rsa-key-size = 3072
renew-by-default
text = True
agree-tos = True
renew-by-default = True
authenticator = webroot
email = admin@example.com
webroot-path = /var/www/letsencrypt/' > /etc/letsencrypt/cli.ini
```

*Please do modify admin@example.com by your actual e-mail address.*

We also need to create the directory structure where letsencrypt ACME challenge temporary files will be stored :

```
mkdir -p /var/www/letsencrypt/.well-known
```

#### nginx configuration

We now need to configure nginx by adding the following in the `/etc/nginx/sites-available/default` file, anywhere in the `server{}` block that is configured to listen on port 80.

```
location /.well-known/acme-challenge {
  root /var/www/letsencrypt;
}
```

Let's make sure that we haven't done anything wrong :

```bash
nginx -t
```

The command should give you the following output :

```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

If that's the case, you can safely reload the nginx daemon :

```
nginx -s reload
```

#### Certificate request

Now that letsencrypt and nginx are properly configured, we can request our certificate from letsencrypt :

```bash
letsencrypt --config /etc/letsencrypt/cli.ini certonly -w /var/www/letsencrypt -d www.example.com
```

*Please do modify www.example.com by your server's FQDN, and please note that the letsencrypt servers need to be able to resolve that name to your server's IP.*

If everything goes well, your certificates will be generated and stored in the /etc/letsencrypt folder.

### WebDAV configuration

Now that we've obtained our certificate from letsencrypt, we can begin configuring nginx.

First, we need to comment two SSL directives from the default nginx configuration :

```
sed -i '/ssl_/ s/^/#/' /etc/nginx/nginx.conf
```

Let's now create a `/etc/nginx/conf.d/ssl.conf` with the following content :

```
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;

ssl_dhparam /etc/nginx/ssl/dhparam.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;

add_header Strict-Transport-Security max-age=15768000;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.1 valid=300s;
resolver_timeout 5s;
```

*This configuration will work if you're using a single certificate on your server. If not, you'll have to remove the `ssl_certificate`, `ssl_certificate_key` and `ssl_trusted_certificate` directives from this file and move them to the correct `server{}` block.*

We now need to generate a `dhparam.pem` file :

```bash
mkdir /etc/nginx/ssl && chmod 700 /etc/nginx/ssl
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 3072
chmod 600 /etc/nginx/ssl/dhparam.pem
```

Let's now generate a HTTP basic authentication file. This example creates a user named example :

```
mkdir /etc/nginx/auth

htpasswd -c /etc/nginx/auth/webdav example
New password:
Re-type new password:
Adding password for user user
```

This file has to be readable by the user running your webserver. For security reasons, we'll make it readable only by him :

```
chown -R www-data:nogroup /etc/nginx/auth
chmod 700 /etc/nginx/auth
chmod 400 /etc/nginx/auth/webdav
```

Let's now modify our `/etc/nginx/sites-available/default` file with the following content :

```
server {
  listen 80 default_server;
  listen [::]:80 default_server ipv6only=on;
  server_name "";

  return 444;
}

server {
  listen 443 default_server ssl http2;
  listen [::]:443 default_server ipv6only=on ssl http2;
  server_name "";

  return 444;
}
```

We now have to create a `/etc/nginx/sites-available/example` file that will contain our actual webdav configuration. This example makes a `data` folder stored in `/var/www/` accessible.

```
server {
  listen 80;
  listen [::]:80;
  server_name www.example.com;
  return 301 https://$server_name$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name www.example.com;

  root /var/www;

  location / {
    index index.html;
  }

  location /.well-known/acme-challenge {
    root /var/www/letsencrypt;
  }

  location /data {
    client_body_temp_path /tmp;
    dav_methods PUT DELETE MKCOL COPY MOVE;
    dav_ext_methods PROPFIND OPTIONS;
    create_full_put_path on;
    dav_access user:r group:r;

    auth_basic "Restricted access";
    auth_basic_user_file auth/webdav;

    limit_except GET {
      allow <YOUR IP HERE>;
      deny all;
    }
  }

}
```

The last thing we have to do is to create a symlink so that nginx will load our configuration :

```
ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
```

Like before, let's make sure our configuration is correct and then reload the daemon :

```
nginx -t
nginx -s reload
```

That's it for the WebDAV configuration server-side !

### nginx monitoring

If you're using monit, you can easily monitor the nginx daemon by copying the following in `/etc/monit/conf.d/nginx` :

```
check process nginx
  with pidfile "/run/nginx.pid"
  start program = "/bin/systemctl start nginx"
  stop program = "/bin/systemctl stop nginx"
  alert monit@example.com
```

### Certificates auto-renewal

This goes beyond the scope of the article, but since letsencrypt certficates are only valid for 3 months, you'll need to renew them regularily. You can do so manually or you can setup a cron that does it for you.

I personnaly use the following script :

```
#!/bin/bash

PRG="/usr/bin/letsencrypt"
CONFIG="/etc/letsencrypt/cli.ini"
MAILDEST="admin@example.com"
GLOBAL=0

# www.example.com
$PRG --config $CONFIG certonly -w /var/www/letsencrypt -d www.example.com
[[ $? != 0 ]] && GLOBAL=$(( $GLOBAL + 1 ))

if [[ $GLOBAL == 0 ]]; then
  /usr/sbin/nginx -s reload
else
  echo "Something went wrong while renewing the certificates on $(hostname -f)
  Manual action needed." | mail -s "Letsencrypt error on $(hostname -f)" $MAILDEST
fi
```

You can add multiple domains in the script. As long as you add all 3 lines for each domain, it will not automatically reload nginx if one or more certificate could not be renewed and will send an e-mail to the address configured in the `MAILDEST` variable.

You can configure this script in the root user crontab using the `crontab -e` command :

```
## LETSENCRYPT CERTIFICATE AUTORENEWAL
30 03 01 */2 * /root/bin/tlsrenew
```
This will run the script every two months, on the first day of the month, at 3:30 AM.

## Client configuration

### Installing the required packages

A single package is required to mount a webdav volume on Debian :

```
apt update && apt install davfs2
```

### Mounting the share manually

If like me, you want to mount your webdav share in a LXC container, you'll first need to make sure that the following line is present in its configuration file :

```
lxc.cgroup.devices.allow = c 10:229 rwm
```

You'll also need to create the `/dev/fuse` node in the container :

```
mknod /dev/fuse c 10 229
```

In any case, we have to edit the `/etc/davfs2/secrets` file to add the mount point, username and password that will be used to mount the share :

```
echo '/data webdav notanactualpassword' >> /etc/davfs2/secrets
```

Once that's done, we can mount our share with the following command :

```
mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
```

You might need to edit the parameters depending on which users you want to make the share available to.

### Mouting the share on boot

A davfs volume can be mounted via the `/etc/fstab` file, but I decided to use monit instead so that the volume would be mounted again automatically should my WebDAV server reboot.

In order to do so, I first created a `davfs.txt` file in the `/var/www/data` folder on my WebDAV server :

```
touch /var/www/data/davfs.txt
```

I then created the following `/root/bin/mount_davfs` script :

```
#!/bin/bash

mknod /dev/fuse c 10 229
mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
```

The last thing I did was create a `/etc/monit/conf.d/davfs` file with the following content :

```
check file davfs with path /data/davfs.txt
  alert monit@example.com
  if does not exist then exec "/root/bin/mount_davfs"
```

That way, if monit notices that the `/data/davfs.txt` file becomes inaccessible for some reason, it will try remouting the share.

## Conclusion

That's all ! Hopefully this has been useful to someone. Please do comment below if you have any question or if this has been helpful !
