Title: WebDAV with nginx Date: 2016-03-26 Category: Tutorial 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 ; 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 !