CentOS 7 用 nginx 构建 WebDAV 服务器

WebDAV (Web-based Distributed Authoring and Versioning) 是一种基于 HTTP 1.1 协议的通信协议, 在GET、POST、HEAD等几个HTTP标准方法以外, 它扩展了 HTTP 1.1, 增加了支持写文件锁定 (Locking) 、解锁 (Unlock) 等方法, 使应用程序可直接对 Web Server 直接读写。

WebDAV 就是通过 Restful API , 实现对服务端文件的 创建 / 删除 / 读取 / 修改。与其他文件传输协议相比, WebDAV 使用 HTTP 传输, 不容易被当作不明流量被砍掉。支持 HTTPS 数据加密, 避免流量被阻断, 并支持 HTTP 2 和范围请求 (RFC7233)

正是因为这些好处, 很多系统和软件都提供了对 WebDAV 的支持。比如说 OS X 的 finder 支持远程连接到 WebDAV 服务器。IOS 的播放器 nPlayer 能够播放 WebDAV 上的视频文件, 且传输速度高于 FTP / SMB 等协议。

准备工作

  • 操作系统: CentOS 7.9.2009 (Core)
  • Nginx 版本: 1.20.2
  • 模块支持
  • 其他依赖:
    • OpenSSL 1.1.1m (TLS 1.3 支持)
    • Zlib 1.2.11 (Gzip 支持)
    • PCRE 8.45 (正则表达式)
    • GCC 9.3.1 20200408 (Red Hat 9.3.1-2)

安装编译环境

yum install epel-release expat-devel httpd-tools unzip wget centos-release-scl git libxslt-devel libxml2-devel -y
yum install devtoolset-9-gcc* -y
yum groupinstall "Development tools" -y

创建非特权服务帐户

创建一个与 EPEL 仓库中相同的 nginx 服务帐户

groupadd -g 994 nginx
useradd -g 994 -u 996 -c "nginx user" -d /var/cache/nginx -s /sbin/nologin nginx

如果你得到下面的输出, 可以忽略此步骤。这只是意味着 nginx 用户已经或已经从仓库安装时创建

[root@centos7 ~]# groupadd -g 994 nginx
groupadd: group 'nginx' already exists
[root@centos7 ~]# useradd -g 994 -u 996 -c "Nginx web server" -d /var/lib/nginx -s /sbin/nologin nginx
useradd: user 'nginx' already exists

下载源码

# 创建工作目录并进入
mkdir nginx-webdav
cd nginx-webdav

# download nginx 1.20.2 source
wget wget https://nginx.org/download/nginx-1.20.2.tar.gz


# download pcre 8.45 / zlib 1.2.11 / openssl 1.1.1m dependency
wget https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.tar.gz
wget http://zlib.net/zlib-1.2.11.tar.gz
wget http://www.openssl.org/source/openssl-1.1.1m.tar.gz


# download nginx-dav-ext-module git.r112.f5e3088
git clone https://github.com/arut/nginx-dav-ext-module.git

# download headers-more-nginx-module git.r259.a4a0686
git clone https://github.com/openresty/headers-more-nginx-module.git


# Extract source file
tar -zxf pcre-8.45.tar.gz
tar -zxf zlib-1.2.11.tar.gz
tar -zxf openssl-1.1.1m.tar.gz
tar -zxf nginx-1.20.2.tar.gz

准备好的文件列表

[root@centos7 nginx-webdav]# tree -L 1
├── headers-more-nginx-module
├── nginx-1.20.2
├── nginx-1.20.2.tar.gz
├── nginx-dav-ext-module
├── openssl-1.1.1m
├── openssl-1.1.1m.tar.gz
├── pcre-8.45
├── pcre-8.45.tar.gz
├── zlib-1.2.11
└── zlib-1.2.11.tar.gz

6 directories, 4 files

编译安装 Nginx

详细参数配置参数

./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib64/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx \
--group=nginx \
--with-zlib=../zlib-1.2.11 \
--with-zlib-opt='-g -Ofast -fPIC -m64 -march=native -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
--with-pcre=../pcre-8.45 \
--with-pcre-opt='-g -Ofast -fPIC -m64 -march=native -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
--with-pcre-jit \
--with-compat \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--add-module=../nginx-dav-ext-module \
--add-module=../headers-more-nginx-module \
--with-openssl=../openssl-1.1.1m \
--with-http_xslt_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module \
--with-mail \
--with-mail_ssl_module \
--with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' \
--with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'

调整文件名长度显示 (可选)

实现网页目录不裁剪文件名

Snipaste_2022-01-30_17-38-15.png

修改 nginx-1.20.2/src/http/modules/ngx_http_autoindex_module.c

#define NGX_HTTP_AUTOINDEX_PREALLOCATE  50

#define NGX_HTTP_AUTOINDEX_NAME_LEN     50

修改为

#define NGX_HTTP_AUTOINDEX_PREALLOCATE  110

#define NGX_HTTP_AUTOINDEX_NAME_LEN     110

nginx: Long filenames in directory listing
Stop nginx from trimming file name in directory list?

综上所述整理好的执行代码

[root@centos7 nginx-webdav]# cd nginx-1.20.2

[root@centos7 nginx-1.20.2]# scl enable devtoolset-9 "./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-zlib=../zlib-1.2.11 --with-zlib-opt='-g -Ofast -fPIC -m64 -march=native -fstack-protector-strong -D_FORTIFY_SOURCE=2' --with-pcre=../pcre-8.45 --with-pcre-opt='-g -Ofast -fPIC -m64 -march=native -fstack-protector-strong -D_FORTIFY_SOURCE=2' --with-pcre-jit --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --add-module=../nginx-dav-ext-module --add-module=../headers-more-nginx-module --with-openssl=../openssl-1.1.1m --with-http_xslt_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'"

[root@centos7 nginx-1.20.2]# scl enable devtoolset-9 "make -j"

[root@centos7 nginx-1.20.2]# ./objs/nginx -V
nginx version: nginx/1.20.2
built by gcc 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC) 
built with OpenSSL 1.1.1m  14 Dec 2021
TLS SNI support enabled
configure arguments: ......

[root@centos7 nginx-1.20.2]# make install

安装完检查 nginx 的版本 nginx -V

[root@centos7 ~]# nginx -V
nginx version: nginx/1.20.2
built by gcc 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC) 
built with OpenSSL 1.1.1m  14 Dec 2021
TLS SNI support enabled
configure arguments: ......

编译报错处理方法

objs/ngx_modules.o \
-Wl,-z,relro -Wl,-z,now -pie -ldl -lpthread -lpthread -lcrypt ../pcre-8.45/.libs/libpcre.a ../openssl-1.1.1m/.openssl/lib/libssl.a ../openssl-1.1.1m/.openssl/lib/libcrypto.a -ldl -lpthread ../zlib-1.2.11/libz.a -lxml2 -lxslt -lexslt \
-Wl,-E
/usr/bin/ld: ../pcre-8.45/.libs/libpcre.a(libpcre_la-pcre_compile.o): relocation R_X86_64_32S against hidden symbol `_pcre_OP_lengths' can not be used when making a shared object
/usr/bin/ld: ../pcre-8.45/.libs/libpcre.a(libpcre_la-pcre_config.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../pcre-8.45/.libs/libpcre.a(libpcre_la-pcre_exec.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../pcre-8.45/.libs/libpcre.a(libpcre_la-pcre_fullinfo.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../pcre-8.45/.libs/libpcre.a(libpcre_la-pcre_jit_compile.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../pcre-8.45/.libs/libpcre.a(libpcre_la-pcre_study.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../zlib-1.2.11/libz.a(deflate.o): relocation R_X86_64_32S against hidden symbol `_length_code' can not be used when making a shared object
/usr/bin/ld: ../zlib-1.2.11/libz.a(inflate.o): relocation R_X86_64_32S against hidden symbol `zcfree' can not be used when making a shared object
/usr/bin/ld: ../zlib-1.2.11/libz.a(inftrees.o): relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../zlib-1.2.11/libz.a(trees.o): relocation R_X86_64_32S against hidden symbol `_length_code' can not be used when making a shared object
/usr/bin/ld: ../zlib-1.2.11/libz.a(zutil.o): relocation R_X86_64_32 against `.rodata.str1.1' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../zlib-1.2.11/libz.a(crc32.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: ../zlib-1.2.11/libz.a(inffast.o): relocation R_X86_64_32S against `.rodata.str1.1' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status
make[1]: *** [objs/nginx] Error 1
make[1]: Leaving directory `../nginx-1.20.2'
make: *** [build] Error 2

如果出现这个错误解决方法是在 ./configure 中加入两行参数

--with-pcre-opt='-g -Ofast -fPIC -m64 -march=native -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
--with-zlib-opt='-g -Ofast -fPIC -m64 -march=native -fstack-protector-strong -D_FORTIFY_SOURCE=2' \

libpcre throwing a “.rodata can not be used when making a shared object” error while compiling NGINX

创建 Nginx 系统服务

创建并编辑系统服务配置文件 /usr/lib/systemd/system/nginx.service

[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/sh -c "/bin/kill -s HUP $(/bin/cat /var/run/nginx.pid)"
ExecStop=/bin/sh -c "/bin/kill -s TERM $(/bin/cat /var/run/nginx.pid)"

[Install]
WantedBy=multi-user.target

设置开机启动并启动 Nginx 服务

systemctl enable nginx
systemctl start nginx

配置 Nginx

站点目录

创建站点目录

mkdir /etc/nginx/conf.d

创建默认 nginx.conf

修改文件 vim /etc/nginx/nginx.conf

user nginx;

worker_processes auto;

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

# Specifies the value for maximum file descriptors that can be opened by this process.
worker_rlimit_nofile 51200;
# PCRE JIT can speed up processing of regular expressions significantly.
pcre_jit on;

events {
    use epoll;
    worker_connections 51200;
    multi_accept on;
}

http {
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server_names_hash_bucket_size 128;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    client_max_body_size 50m;

    charset utf-8;
    sendfile on;
    server_tokens off;
    tcp_nodelay on;
    tcp_nopush on;
    real_ip_header X-Forwarded-For;
    types_hash_max_size 2048;
    keepalive_timeout 60;
    access_log /var/log/nginx/access.log main;


    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 256k;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 2;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_vary on;
    gzip_proxied expired no-cache no-store private auth;
    gzip_disable "MSIE [1-6]\.";

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

        if ($host ~ "\d+\.\d+\.\d+\.\d+") {
            return 404;
        }
        return 301 https://$host$request_uri;
    }

    include /etc/nginx/conf.d/*.conf;
}

WebDAV 账户管理

使用 htpasswd 创建 WebDAV 访问账户

yum install httpd-tools -y

# 创建用户名为 rbq 的用户
htpasswd -c /home/SSL/webdav.htpasswd 'rbq'

都配置完之后重启 nginx

nginx -s reload

设置文件夹访问权限

WebDAV 主目录在 /home 下访时, 需要调整权限

nginx 用户加入 qaq 用户组, 并将 qaq 用户加入 nginx

[root@centos7 ~]# id qaq
uid=1000(qaq) gid=1000(qaq) groups=1000(qaq)

[root@centos7 ~]# id nginx
uid=996(nginx) gid=994(nginx) groups=994(nginx)

[root@centos7 ~]# usermod -G qaq nginx
[root@centos7 ~]# usermod -G nginx qaq

[root@centos7 ~]# id nginx
uid=996(nginx) gid=994(nginx) groups=994(nginx),1000(qaq)

修改文件夹权限为 774, 确保共享目录的读写权限

chmod -R 774 /home/qaq

创建 WebDAV 站点

修改文件 vim /etc/nginx/conf.d/webdav.conf

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

    ssl_certificate "/home/SSL/example.com.crt";
    ssl_certificate_key "/home/SSL/example.com.key";
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 30m;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES;
    ssl_prefer_server_ciphers on;

    access_log /var/log/nginx/access-example.com.log main;
    error_log /var/log/nginx/error-example.com.log error;

    location / {
        # 设置 webdav 目录, 注意 Nginx worker 用户对该目录需有读/写/执行权限
        root /home/qaq;

        auth_basic "closed site";
        auth_basic_user_file /home/SSL/webdav.htpasswd;

        dav_methods PUT DELETE MKCOL COPY MOVE;
        dav_ext_methods PROPFIND OPTIONS;

        # 启用完整的创建目录支持
        create_full_put_path on;
        dav_access user:rw group:rw;

        autoindex on;
        autoindex_localtime on;
        autoindex_exact_size off;
        # 不限制文件大小
        client_max_body_size 0;

        # 为各种方法的URI后加上斜杠, 解决各平台webdav客户端的兼容性问题
        set $dest $http_destination;
        if (-d $request_filename) {
            rewrite ^(.*[^/])$ $1/;
            set $dest $dest/;
        }

        if ($request_method ~ (MOVE|COPY)) {
            more_set_input_headers 'Destination: $dest';
        }

        if ($request_method ~ MKCOL) {
            rewrite ^(.*[^/])$ $1/ break;
        }
    }

    # Mac 挂载 webdav 后会自动写入很多文件, 可以通过 nginx 配置屏蔽掉, 保持 webdav 目录的干净
    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 "Don't index this drive, Finder!";
    }
}

本套配置经过 4 篇文章整理而成

  1. 开启 TLSv1.2 TLSv1.3, 并且平衡了兼容性和安全性. SSL Labs 评分为 A
  2. 开启 http 基本验证
  3. dav 权限设置为 770 dav_access user:rw group:rw;
  4. 启用 nginx 自带的网页目录列表
  5. 修复某些客户端路径结尾没有斜杠
  6. 屏蔽 Mac 挂载 webdav 后会自动写入很多文件

Snipaste_2022-01-31_06-54-20.png

Nginx 安装和基本配置完成后, WebDAV 服务已生效。如果你在 Web 浏览器中访问 WebDav, 应该能够看到该目录中现有文件的目录列表, 但你无法通过 Web 浏览器上传新文件。需要使用 WebDAV 客户端 (例如 macOS Finder、Windows 中的 rclone 等) 管理文件。

配置代码解析

某些 WebDAV 客户端 (如 OS X 下的 ForkLift) 在 创建文件夹 / 复制文件夹 / 移动文件夹 失败的问题

Nginx 默认要求对文件夹操作时路径结尾需要有 /, 而部分客户端没有遵守这一要求。解决方法是用 url rewrite, 如果操作的是文件夹, 则补上 /

2018/06/03 08:03:58 [error] 7#7: *1 MKCOL can create a collection only, client: 8.8.8.8, server: example.com, request: "MKCOL /sp HTTP/2.0", host: "example.com"
2018/06/03 08:17:20 [error] 7#7: *18 "/sp" is collection, client: 8.8.8.8, server: example.com, request: "MOVE /sp HTTP/2.0", host: "example.com"

以下代码是为了解决某些 WebDAV 客户端 (如 macOS 下的 ForkLift) 在创建文件夹、复制文件夹或移动文件夹时因路径结尾缺少斜杠而报错的问题。

set $dest $http_destination;
if (-d $request_filename) {
    rewrite ^(.*[^/])$ $1/;
    set $dest $dest/;
}
if ($request_method ~ (MOVE|COPY)) {
    more_set_input_headers 'Destination: $dest';
}

if ($request_method ~ MKCOL) {
    rewrite ^(.*[^/])$ $1/ break;
}

配置说明

  1. 路径补全: 使用 rewrite 补全文件夹路径末尾的 /, 确保兼容客户端的操作习惯
  2. MOVE / COPY 方法兼容性: 对 MOVE 和 COPY 操作, 将 Destination 头的路径末尾补上 /, 以满足 Nginx 的 WebDAV 需求 (需要 headers-more-nginx-module 模块)
  3. MKCOL 方法补全: 确保创建集合 (文件夹) 时路径的正确性, 避免路径缺少 / 导致的 MKCOL 报错

WebDAV 支持的请求方法

  • OPTIONS: 查询 WebDAV 服务支持的方法
  • GET: 获取文件
  • PUTPOST: 上传文件
  • DELETE: 删除文件或集合
  • COPY: 复制文件
  • MOVE: 移动文件
  • MKCOL: 创建新的文件集合 (文件夹)
  • PROPFIND: 查询文件属性
  • LOCKUNLOCK: 加锁、解锁文件, 实现写保护

使用 curl 测试 WebDAV

使用 curl 测试时, URI后面一定要带 / , 不然就会报错

创建目录

curl -X MKCOL -u USER:PASSWORD https://dav.example.com/test/

上传文件

curl -T FILE -u USER:PASSWORD https://dav.example.com/test/

重命名

curl -X MOVE -u USER:PASSWORD --header 'Destination:https://dav.example.com/test/newname' https://dav.example.com/test/File

删除

curl -X DELETE -u USER:PASSWORD https://dav.example.com/test/File

原文 - 编译贡献 (文章按照贡献价值排序)

在 CentOS 7 上从源代码安装 Nginx
How to Build Nginx from source on CentOS 7
Centos7.x 编译安装全功能的Nginx

原文 - 配置贡献 (仅配置部分可供参考 其余部分没啥用)

nginx配置功能完整的webdav服务器
使用 nginx 搭建 WebDAV 服务器
Install Nginx as a WebDav File Server on CentOS 7
如何在 Nginx 上启用 TLS 1.3

最后更新于 2022-03-12
使用 Hugo 构建
主题 StackJimmy 设计