Introduction
WebDAV (Web-based Distributed Authoring and Versioning) extends HTTP with methods for remote file operations, enabling web servers to function as full-featured file servers with locking, metadata, and directory support. Unlike plain HTTP file uploads, WebDAV provides a structured protocol for collaborative editing where multiple users can read, write, lock, and organize files on a remote server through standard HTTP verbs and XML request bodies.
This guide covers the WebDAV protocol methods, server configuration for Apache and Nginx, a complete Python client implementation with detailed explanation of each operation, file locking semantics, and a comparison with alternative approaches (S3, FTP, SCP) so you can choose the right tool for your use case.
How WebDAV Works
WebDAV operates as an HTTP extension using existing verbs (GET, PUT, DELETE) plus new methods (PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK). A WebDAV client communicates with a WebDAV-enabled server by sending these HTTP requests, and the server responds with XML bodies describing resources, properties, and lock status.
sequenceDiagram
participant Client as WebDAV Client
participant Server as WebDAV Server
Client->>Server: PROPFIND / (list directory)
Server-->>Client: 207 Multi-Status (XML with file list)
Client->>Server: GET /docs/report.pdf (download)
Server-->>Client: 200 OK (binary data)
Client->>Server: LOCK /docs/report.pdf (prevent conflicts)
Server-->>Client: 200 OK (Lock-Token header)
Client->>Server: PUT /docs/report.pdf (upload new version)
Server-->>Client: 204 No Content
Client->>Server: UNLOCK /docs/report.pdf (release lock)
Server-->>Client: 204 No Content
The XML responses use the DAV: namespace and contain property data for each resource. This design lets clients discover file metadata without separate API calls — a single PROPFIND returns file sizes, modification dates, content types, and custom properties for an entire directory tree.
WebDAV Methods
Standard HTTP + WebDAV Methods
| Method | Description | Request Body | Response |
|---|---|---|---|
| GET | Read file contents | None | File binary data |
| PUT | Upload or overwrite a file | File binary data | 200/201/204 |
| DELETE | Remove a file or collection | None | 200/204 |
| PROPFIND | Retrieve resource properties and list directory contents | XML (depth, prop specs) | 207 Multi-Status (XML) |
| PROPPATCH | Modify resource properties | XML (property updates) | 207 Multi-Status |
| MKCOL | Create a new directory (collection) | None | 201 Created |
| COPY | Copy a resource to a new location | XML (destination, depth) | 201/204 |
| MOVE | Move or rename a resource | XML (destination) | 201/204 |
| LOCK | Lock a resource for exclusive or shared write access | XML (lock scope, owner) | 200 + Lock-Token |
| UNLOCK | Release a lock | Lock-Token header | 204 No Content |
PROPFIND in Detail
PROPFIND is the most distinctive WebDAV method. The client sends a Depth header (0 for single resource, 1 for immediate children, infinity for entire tree) and optionally an XML body requesting specific properties:
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<displayname/>
<getcontentlength/>
<getcontenttype/>
<getlastmodified/>
<resourcetype/>
</prop>
</propfind>
The server responds with a 207 Multi-Status containing a <multistatus> element with <response> entries for each resource. Each response includes the resource URL (<href>) and the requested property values. This is how the client discovers what files and directories exist at a given path without needing a separate index.
Server Configuration
Apache WebDAV
Apache supports WebDAV through the mod_dav and mod_dav_fs modules. The configuration below sets up a password-protected WebDAV share with HTTPS:
# /etc/apache2/sites-available/webdav.conf
<VirtualHost *:443>
ServerName dav.example.com
DocumentRoot /var/www/webdav
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
DAV On
Options +Indexes
<Location /webdav>
DAV On
AuthType Basic
AuthName "WebDAV"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</Location>
</VirtualHost>
This config enables WebDAV on the /webdav path with basic authentication. The AuthUserFile is created with htpasswd -c /etc/apache2/.htpasswd username. For production, prefer digest authentication or integrate with LDAP, and always use HTTPS since Basic auth transmits credentials in every request.
Enable the required modules and restart Apache:
sudo a2enmod dav dav_fs
sudo a2ensite webdav.conf
sudo systemctl restart apache2
Nginx WebDAV
Nginx supports WebDAV through the ngx_http_dav_module, though it lacks PROPFIND and locking support in the default module. For full WebDAV support, use the nginx-dav-ext-module (compile Nginx with --add-module=nginx-dav-ext-module):
# /etc/nginx/sites-available/webdav
server {
listen 443 ssl;
server_name dav.example.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
root /var/www/webdav;
# Enable WebDAV methods
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND PROPPATCH;
# Require authentication for writes
auth_basic "WebDAV";
auth_basic_user_file /etc/nginx/.htpasswd;
# Allow creating files with PUT
create_full_put_path on;
dav_access user:rw group:rw all:r;
location / {
autoindex on;
}
}
The create_full_put_path on directive lets clients create intermediate directories automatically when uploading files — useful for clients that structure files in nested paths. Nginx’s built-in module handles the basic WebDAV methods, and the extension module adds property retrieval and modification.
Python WebDAV Client
The following client class implements the core WebDAV operations using Python’s requests library. Each method maps to a specific HTTP verb and handles the corresponding response codes.
import requests
from requests.auth import HTTPBasicAuth
class WebDAVClient:
"""A lightweight WebDAV client that wraps HTTP requests.
Each method corresponds to a WebDAV operation. The client
maintains an authenticated session so credentials are sent
with every request automatically.
"""
def __init__(self, base_url, username, password):
"""Initialize with server URL and credentials.
Args:
base_url: Root URL of the WebDAV server (e.g. https://dav.example.com/webdav)
username: WebDAV authentication username
password: WebDAV authentication password
"""
self.base_url = base_url
self.session = requests.Session()
self.session.auth = HTTPBasicAuth(username, password)
def upload(self, local_path, remote_path):
"""Upload a local file to the WebDAV server.
Uses HTTP PUT to write file contents. The server creates
or overwrites the resource at the given remote path.
Returns True on success (201 Created or 204 No Content).
"""
with open(local_path, 'rb') as f:
response = self.session.put(
f"{self.base_url}/{remote_path}",
data=f
)
# 200 OK (overwrite), 201 Created (new), 204 No Content (accepted)
return response.status_code in (200, 201, 204)
def download(self, remote_path, local_path):
"""Download a remote file and save it locally.
Uses HTTP GET to retrieve the resource content.
Returns True only on 200 OK.
"""
response = self.session.get(f"{self.base_url}/{remote_path}")
with open(local_path, 'wb') as f:
f.write(response.content)
return response.status_code == 200
def list_files(self, path='/'):
"""List resources at the given path.
Sends a PROPFIND request with Depth: 1 to retrieve
immediate children. The server returns XML with resource
URLs and properties. This method returns the raw XML
response text for parsing.
"""
response = self.session.request(
'PROPFIND',
f"{self.base_url}/{path}",
headers={'Depth': '1'} # 0=self, 1=children, infinity=all
)
return response.text
def delete(self, path):
"""Delete a resource (file or empty directory).
Uses HTTP DELETE. The server returns 200 or 204 on success.
"""
response = self.session.delete(f"{self.base_url}/{path}")
return response.status_code in (200, 204)
def create_directory(self, path):
"""Create a new directory (collection).
Uses the MKCOL method. The server creates the collection
and returns 201 Created on success.
"""
response = self.session.request('MKCOL', f"{self.base_url}/{path}")
return response.status_code in (200, 201)
Usage Example
Here is how you use the client to interact with a WebDAV server:
client = WebDAVClient(
base_url="https://dav.example.com/webdav",
username="alice",
password="secure-pass"
)
# Upload a file
if client.upload("report.pdf", "docs/report.pdf"):
print("Upload succeeded")
# List directory contents
files_xml = client.list_files("docs/")
print(files_xml) # XML with file names, sizes, modification dates
# Download a file
if client.download("docs/report.pdf", "downloaded_report.pdf"):
print("Download succeeded")
# Create a new directory
client.create_directory("docs/archive")
# Delete a file
client.delete("docs/report.pdf")
File Locking
WebDAV provides two locking mechanisms to prevent conflicting edits: exclusive locks (one user at a time) and shared locks (multiple readers). Locks are managed through the LOCK and UNLOCK methods with an XML body specifying the lock scope and type.
How Locking Works
def lock(self, path, timeout=60):
"""Acquire an exclusive write lock on a resource.
The XML body specifies:
- lockscope: exclusive (single writer) or shared (multiple)
- locktype: write (prevents writes from others)
The server returns a Lock-Token header that must be
included in subsequent UNLOCK requests.
"""
headers = {
'Timeout': f'Second-{timeout}',
'Content-Type': 'application/xml'
}
body = '''<?xml version="1.0"?>
<d:lockinfo xmlns:d="DAV:">
<d:lockscope><d:exclusive/></d:lockscope>
<d:locktype><d:write/></d:locktype>
</d:lockinfo>'''
response = self.session.request(
'LOCK',
f"{self.base_url}/{path}",
headers=headers,
data=body
)
# Returns the opaque token string, or None if lock failed
return response.headers.get('Lock-Token')
def unlock(self, path, lock_token):
"""Release a previously acquired lock.
The Lock-Token value from the LOCK response must be
sent in the Lock-Token header to identify which lock
to release.
"""
headers = {'Lock-Token': lock_token}
response = self.session.request(
'UNLOCK',
f"{self.base_url}/{path}",
headers=headers
)
return response.status_code in (200, 204)
The XML body sent with the LOCK request tells the server what kind of lock is requested:
- Exclusive lock: Only the lock owner can modify the resource. Other clients that attempt to PUT or PROPPATCH the locked resource receive a 423 Locked status.
- Shared lock: Multiple clients can hold the lock simultaneously. Useful for collaborative editing where multiple users read the same file.
The Lock-Token returned by the server is an opaque URL (e.g., urn:uuid:...). Clients must store this token and include it when unlocking or modifying the locked resource. If a client crashes after locking but before unlocking, an administrator can break the lock through server-side tools (e.g., Apache’s dav_fs provides lock management utilities).
Best Practices
Always use HTTPS. WebDAV transmits authentication credentials and file contents over the wire. Without TLS, credentials sent in Basic auth headers are base64-encoded (not encrypted) and trivially intercepted.
Implement proper authentication. Basic auth over HTTPS is adequate for most use cases. For enterprise deployments, integrate with LDAP, Active Directory, or OAuth2.
Use locks for concurrent editing. Without locking, two clients can overwrite each other’s changes silently. Locks serialize writes and prevent conflicts. Set timeouts appropriate to your workload — long enough for a single edit session (60-300 seconds), short enough that abandoned locks are released promptly.
Set appropriate timeout values. The Timeout header in LOCK requests specifies seconds before the lock auto-expires. Servers may enforce maximum timeouts (Apache defaults to 120 seconds). The actual timeout is the minimum of client request and server limit.
Consider client compatibility. Not all operating systems and file managers support WebDAV equally. macOS Finder and Windows Explorer have built-in WebDAV support. Linux users typically mount via davfs2. Mobile support is limited — use a dedicated client app or your own Python client.
Alternatives Comparison
| Feature | WebDAV | S3 API | FTP/SFTP | SCP |
|---|---|---|---|---|
| Protocol | HTTP/HTTPS | HTTP/HTTPS | TCP port 21/22 | SSH (port 22) |
| Directory listing | PROPFIND (XML) | GET ?list-type=2 | LIST/MLSD | None |
| Locking | Built-in (LOCK/UNLOCK) | None native | None | None |
| Metadata | Arbitrary properties via PROPPATCH | Tags + user metadata | File modification time only | File modification time |
| Firewall friendly | Yes (port 443) | Yes (port 443) | No (requires multiple ports) | Yes (port 22) |
| Client support | OS file managers, Python, JS | AWS SDK, HTTP | CLI, GUI clients | CLI (scp) |
| Best for | Collaborative editing, cloud storage | Object storage, CDN, data lakes | Server file transfer | Quick one-off file copy |
WebDAV excels in scenarios where users need to edit files remotely with conflict prevention, especially in enterprise environments with existing HTTP infrastructure. S3 is better suited for scalable object storage with high availability. FTP/SFTP remains practical for bulk server-to-server transfers. SCP is the simplest option for ad-hoc file copying but offers no directory browsing or locking.
Conclusion
WebDAV fills a specific niche: HTTP-based remote file management with locking and metadata support. It integrates naturally with existing web infrastructure (same ports, same authentication mechanisms) and works with operating system file managers for a native experience. For collaborative editing workflows where multiple users need to read, write, and organize files on a remote server with conflict prevention, WebDAV remains the best available standard protocol.
The Python client implementation in this guide gives you a foundation you can extend with XML response parsing, recursive upload/download, and progress reporting — all built on the simple HTTP request model.
Resources
- RFC 4918 — HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)
- Apache mod_dav Documentation
- Nginx ngx_http_dav_module
- nginx-dav-ext-module GitHub — Full PROPFIND/PROPPATCH support for Nginx
- Python requests library
- davfs2 — Linux WebDAV filesystem
Comments