I'm looking to completely decommission my SMBA service and replace it with a WebDav service.

All the google searches so far have pointed me to using Apache / Webdav. This is close to what I need but as far as I read it requires Apache to have access to my user's files and worse; if it creates a file the new file will be owned by Apache (not the user). Note that having files with the correct Unix ownership and permissions is a requirement as some users have direct SSH access.

So I'm quite simply looking for a way to either make Apache / Webdav work "correctly" with multi-users (that is change unix user to the logged in user before attempting to serve the file) or find a complete alternative to Apache / Webdav.

So far searches haven't turned anything up.

  • As webdav is based on the HTTP protocol, I would say it's not existing except under the form of an HTTP server. And if you find product that offer webdav trhey will usually offer more than that
    – Kiwy
    Apr 3, 2014 at 18:50
  • It looks like there may be something promising in the latest version of MPM ITK. mpm-itk.sesse.net I'll have a try with this and see if AssignUserIDExpr will accept the logged in user. It may not since AssignUserID appears to kick in before the user authenticates. Apr 7, 2014 at 11:09
  • There are standalone webdav servers like code.google.com/p/opendav or libraries like PyWebDAV that don't require apache.
    – Jan
    Aug 21, 2014 at 7:58
  • @jan That may turn out to to be the best answer. Apache is running on the server already and webdav should be a subdirectory on the site but I can set that up as a proxy pass through and use Apache's SSL to provide the encryption. Aug 25, 2014 at 22:37
  • 1
    Should be moved to Software Recommendations.SE.
    – William
    Oct 18, 2014 at 19:55

9 Answers 9


If you have the username and/or the uid, you can do this with nginx + lua + luarocks ljsyscall

On a debian system, configured as:

apt-get -y install nginx libnginx-mod-http-dav-ext libnginx-mod-http-lua luarocks
luarocks install ljsyscall

And nginx configured the following way:

user  root;
worker_processes  1;

load_module modules/ngx_http_dav_ext_module.so;
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_lua_module.so;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;

http {
    sendfile        on;
    keepalive_timeout  65;
    gzip  on;

    server {
        listen      80;
        listen [::]:80;

        location / {
            rewrite ^ http://$host$request_uri?; # permanent;

    server {
        listen      443           ssl http2;
        listen [::]:443           ssl http2;

        ssl                       on;    
        # [ SSL Sections Omitted ]

        # Set the maximum size of uploads
        client_max_body_size 200m;

        # Default is 60, May need to be increased for very large uploads
        client_body_timeout 120s; 

        # other configs
        location /webdav/ {
            autoindex              on;
            alias                  /data/www/;
            client_body_temp_path  /data/client_temp;

            dav_methods PUT DELETE MKCOL COPY MOVE;
            dav_ext_methods PROPFIND OPTIONS;

            create_full_put_path   on;
            # Not sure if you want to tweak this
            # dav_access             group:rw  all:r;

            # Let's assume you have an auth subrequest that can set X-UID
            auth_request  /auth
            auth_request_set $auth_status $upstream_status;
            auth_request_set $saved_remote_user $upstream_http_REMOTE_USER;
            auth_request_set $saved_remote_uid $upstream_http_X_UID;

            # Per-Request Impersonation
            access_by_lua_block {
                # Boilerplate because ljsyscall doesn't have setfsuid implemented directly
                local syscall_api = require 'syscall'
                local ffi = require "ffi"
                local nr = require("syscall.linux.nr")
                local sys = nr.SYS
                local uint = ffi.typeof("unsigned int")
                local syscall_long = ffi.C.syscall -- returns long
                local function syscall(...) return tonumber(syscall_long(...)) end 
                local function setfsuid(id) return syscall(sys.setfsuid, uint(id)) end
                -- If you only have ngx.var.saved_remote_user, install luaposix and do this ...
                -- local pwd = require 'posix.pwd'
                -- local new_uid = pwd.getpwnam(ngx.saved_remote_user).pw_uid
                local new_uid = tonumber(ngx.var.saved_remote_uid)
                ngx.log(ngx.NOTICE, "[Impersonating User #" .. new_uid .. "]")
                local previous = setfsuid(new_uid)
                local actual = setfsuid(new_uid)
                if actual ~= new_uid then
                    ngx.log(ngx.CRIT, "Unable to impersonate users")

        location = /auth {
            proxy_pass              http://localhost:8080/auth;
            proxy_pass_request_body off;
            proxy_set_header        Content-Length "";
            proxy_set_header        X-Original-URI $request_uri;
            proxy_set_header        X-Original-Method $request_method;

This will execute setfsuid on every request serviced by the nginx worker. Unfortunately, it seems you must be running nginx as root in order for this to work right currently. I believe it's possible for this to work with a different user provided the process started as root, dropped to a different user, with CAP_SETUID preserved (see documentation for capsh), and the user directive is absent in the nginx config file.

You may also need to set the group IDs, potentially.

See "Effect of user ID changes on capabilities" in http://man7.org/linux/man-pages/man7/capabilities.7.html

  • That looks promising. I'll check it out. Dec 19, 2018 at 10:07

Having searched for a long time I just couldn't find one. There are many multi user servers but I couldn't find one which executed as the system user.

So I wrote one myself. This is only tested as far as I can test it myself. But for what its worth, the source code is here:




I was looking for same thing and I finally gathered a solution using apache2. I tried node solution using npm webdav-server and found out not all worked as nicely then using the apache module. Then I tried a npm dav-server based on jsDAV that could do better and could be a solution, but as I had to deal with lousy 3g connection I preferred apache and found out about multiple instance scripts.

So here I share my experience.


I run an instance per webdav user... not very scalable, but to work in a small team it's good enough.

Replace myUser with your user.

On Ubuntu 14.04

sh /usr/share/doc/apache2/examples/setup-instance myUser

So I run a apache process as user myUser defined in /etc/apache2-myUser/envars

export APACHE_RUN_USER=myUser
export APACHE_RUN_GROUP=myUser

Edit ports.conf

# If you proxy with nginx as I did better to limit to local interface
listen localhost:8080
# listen 8080

I could not get PAM auth on ubuntu 14.04 to work so need to trick with basic auth as I then wrap it in https with nginx

htpasswd -c /etc/apache2/htpasswd myUser

Then /etc/apache2-myUser/sites-available/000-default.conf

<VirtualHost *:8080>

DocumentRoot /var/www/html

<Directory /home/${APACHE_RUN_USER}>
    Require all granted
    Options +Indexes

<Location /${APACHE_RUN_USER}>
      DAV On
      AuthType Basic
      AuthName "Restricted Area"
      AuthUserFile /etc/apache2/htpasswd
      Require valid-user

DavLockDB /home/${APACHE_RUN_USER}/.DavLock
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

then nginx proxy has a trick with header Destination passing icons folder lets webdav downgrade nicely on browsers

server {
listen 443 ssl http2;
server_name exemple.com;

location ~ ^/(myUser|icons)/ {

    proxy_pass http://dav-myUser;

#         auth_basic "Restricted Content";
#         auth_basic_user_file /etc/nginx/htpasswd;

#         proxy_set_header Authorization $http_authorization;

    proxy_pass_header  Authorization;
    proxy_pass_request_headers on;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;

    port_in_redirect off;

    # to avoid 502 Bad Gateway:
    # http://vanderwijk.info/Members/ivo/articles/ComplexSVNSetupFix
    set $destination $http_destination;

    if ($destination ~* ^https(.+)$) {
        set $destination http$1;

    proxy_set_header Destination $destination;

    proxy_read_timeout     300;
    proxy_connect_timeout  5;

    # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
    proxy_http_version 1.1;

    # Remove the Connection header if the client sends it,
    # it could be "close" to close a keepalive connection
    proxy_set_header Connection "";

ssl on;
ssl_certificate /etc/letsencrypt/live/exemple.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/exemple.com/privkey.pem;

include /etc/letsencrypt/options-ssl-nginx.conf;


There is no obligation to use nginx as proxy, apache could very well do the https, but as I bumped into the proxy Destination issue I felt it was worth mentioning it.


I have recently set up a webdav server with PAM authentichation using Nginx and here is the instruction:

Firstly, we need to install these packages:

sudo apt-get install nginx libnginx-mod-http-dav-ext libnginx-mod-http-lua libnginx-mod-http-headers-more-filter libnginx-mod-http-auth-pam libnginx-mod-http-dav-ext

Then, we need to add one line in the http block of your nginx.conf file:

# The webdav has to run as root
user root www-data;

http {
    # dav lock zone
    # https://github.com/arut/nginx-dav-ext-module
    dav_ext_lock_zone   zone=dav:10m;

We also need to create a server block for the webdav server:

server {
    server_name example.com;

    # other server and ssl configs

    # ===== macOS junk filter =====
    # https://www.robpeck.com/2020/06/making-webdav-actually-work-on-nginx/
    location ~ \.(_.*|DS_Store|Spotlight-V100|TemporaryItems|Trashes|hidden|localized)$ {
        access_log  off;
        error_log   off;

        if ($request_method = PUT) {
            return 403;
        return 404;
    location ~ \.metadata_never_index$ {
        return 200;
    # ===== macOS junk filter =====

    # webdav handler
    location / {
        # PAM authentication
        # https://github.com/sto/ngx_http_auth_pam_module
        # you can also use other authentications such as basic:
        # https://www.digitalocean.com/community/tutorials/how-to-set-up-basic-http-authentication-with-nginx-on-ubuntu-14-04
        auth_pam               "dav";
        auth_pam_service_name  "example.com";

        # optional - disable root login
        if ($remote_user = "root") {
            return 403;

        # user directory
        root             /mnt/dav/$remote_user/Data;

        # setup dav modules
        # http://nginx.org/en/docs/http/ngx_http_dav_module.html
        # https://github.com/arut/nginx-dav-ext-module
        dav_methods             PUT DELETE MKCOL COPY MOVE;
        dav_ext_methods         PROPFIND OPTIONS LOCK UNLOCK;
        dav_ext_lock            zone=dav;

        # permission of the uploaded files
        dav_access       user:rw;

        # disable file size restriction
        # http://nginx.org/en/docs/http/ngx_http_core_module.html
        client_max_body_size    0;

        # automatically create directory structure for new files
        create_full_put_path    on;

        # you can set the upload cache location here 
        # client_body_temp_path /mnt/dav/Temp

        # ===== macOS fix =====
        # https://www.robpeck.com/2020/06/making-webdav-actually-work-on-nginx/
        if ($request_method = MKCOL) {
            rewrite ^(.*[^/])$ $1/ break;
        set $destination $http_destination;
        set $parse "";

        if ($request_method = MOVE) {
            set $parse "${parse}M";

        if ($request_method = COPY) {
            set $parse "${parse}M";

        if (-d $request_filename) {
            rewrite ^(.*[^/])$ $1/ break;
            set $parse "${parse}D";

        # ==== dont forget to update this line to your hostname ====
        if ($destination ~ ^https://example.com/(.*)$) {
            set $ob $1;

            # unescape uri to keep the original filename
            set_by_lua_block $ob {
                return ngx.unescape_uri(ngx.var.ob);
            set $parse "${parse}R${ob}";

        if ($parse ~ ^MDR(.*[^/])$) {
            set $mvpath $1;
            # unescape uri to keep the original filename
            set_by_lua_block $mvpath {
                return ngx.unescape_uri(ngx.var.mvpath);
            # ==== dont forget to update this line to your hostname ====
            set $destination "https://example.com/${mvpath}/";
            more_set_input_headers "Destination: $destination";
        # ===== macOS fix =====

        # change the owner of the files from root to the user
        log_by_lua_block {
            local function update_file_owner(file)
                # only update ownership for write operations
                if  ngx.var.request_method == "COPY" or 
                    ngx.var.request_method == "PUT" or
                    ngx.var.request_method == "MKCOL" or
                    ngx.var.request_method == "MOVE"
                    os.execute('chown "' .. ngx.var.remote_user .. '.' .. ngx.var.remote_user .. '" "' .. ngx.var.request_filename .. '"')

sftpgo has an admin ui where you can add/delete users and permissions. Then it opens ports for webdav, ftp, and sftp and hosts the files there. Users can use the same username/password for all three protocols.


This may be worth to read: Another input: multiple user folders and one shared folder http://hexeract.wordpress.com/2011/02/25/configure-a-webdav-enabled-webserver-for-multiple-user-folders-and-one-shared-folder/

  • 2
    This has the same problem as your other answer. Some users have ssh access. The files MUST be given correct (their own, not the web server's) unix file permissions and ownership (both user and group). Oct 20, 2014 at 10:49

I used this one as guide to set up webdav: http://bernaerts.dyndns.org/linux/75-debian/62-debian-webdav-share

yes, Apache is the group (www-data under Debian) but you can add users to that group, so I added one user. Did not test why you may not add other users.... The webdav server using in principle this setup runs now for 3 yrs at my and my sons place (so 2 identical servers for the work of my son). Debian 6 is since some months the LTS version (till Feb-2016).

Compared to Bernaerts I adapted in the Apache file: /etc/apache2/sites-available/default this part of configuration.

Alias /webdav1 /data/webdav1

<Location /webdav1>
DAV on
Authtype Basic
Authname "webdav1"
AuthUserFile /var/www/web1/passwd1.dav
Require valid-user

So my files are no longer under www but in /data/webdav1 (through alias webdav1 to keep it short) For each hard disk I've created such a section and webdav1 becomes webdav2 for the 2nd hard disk in that section. We can build in max 10 hard disks in these servers, so 10 of these section in that config file. I added the user to www-data, davfs2 and davfs, so that the user can have access the webdav folder(s). So the user need to login and will be asked for the username and password. In fstab all the webdav data disks are listed so that mounting proceeds automaticallly. That part of fstab:

/dev/sda3 /data/webdav1 ext3, user,auto 0 0

  • 4
    Sadly this doesn't solve the problem at all. The focus of this question was multi user. With this solution new files will be created as the apache user not the logged in user. In order to function apache all files must be www-data group with read / write permissions to that group. Since every user will have to be in that group every user will have to have access to read / write every other user's files. This solution simply doesn't wok for multiple users. Oct 20, 2014 at 10:45

Have you tried OwnCloud? Still just testing it myself, but it sounds like it fills your requirements: webdav works out-of-the-box.

  • 4
    Yes I have an instance of Owncloud but thats not what I'm looking for because the owncloud user (apache) owns all the files. May 31, 2016 at 9:04

I'm also looking for a similar solution.

Solution 1: Your desktop environment (Gnome, KDE) might have widgets to expose a certain folder by WebDAV. This will run as long as your desktop environment is running and is not a daemon solution.

Solution 2: Nothing stops you from running Apache under your own user binding on unprivileged ports above 1024. Just write a configuration file or copy the ones bundled in your distribution to your $HOME/etc/httpd (just an example), add DAV-related config and run it as your own non-root user like:

$ httpd -f $HOME/etc/httpd

Running as your users ensures Apache will create files as you.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .