# WebDAV with nginx

26 March 2016

This website has been hosted on an 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 and Let’sEncrypt 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 :

    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 :

    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 :

    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 :

    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 :

    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 !

## Installing Ghost

## MySQL backup script