WebDAV Server with aiohttp
A comprehensive, production-ready WebDAV server implementation with full RFC 4918 compliance, Windows Explorer compatibility, and enterprise-grade features.
Features
Core WebDAV Protocol
- ✅ Full RFC 4918 Compliance : All standard WebDAV methods implemented
- ✅ Windows Explorer Integration : Seamless compatibility with Windows WebDAV Mini-Redirector
- ✅ Multiple Authentication Methods : Basic, Digest, and Token-based authentication
- ✅ Resource Locking : Exclusive and shared locks with timeout management
- ✅ Custom Properties : Full property management (PROPFIND/PROPPATCH)
- ✅ Collection Operations : MKCOL, COPY, MOVE with depth support
Enterprise Features
- 🔒 Multi-User Support : Isolated user directories with permissions
- 💾 SQLite Database : Robust backend for users, locks, and properties
- 🌐 Web Management Interface : Admin dashboard and user portal
- 🚀 High Performance : Async I/O with aiohttp and aiofiles
- 🔐 Security Focused : Input validation, path traversal prevention, SQL injection protection
- 📊 Monitoring Ready : Structured logging and metrics collection
- 🐳 Docker Support : Container-ready with docker-compose
- 📈 Production Ready : Gunicorn integration, health checks, graceful shutdown
Quick Start
Prerequisites
- Python 3.8 or higher
- pip (Python package manager)
- Optional: Redis for caching
- Optional: Docker for containerized deployment
Installation
-
Clone or create the project directory:
mkdir webdav-server
cd webdav-server -
Install dependencies:
pip install -r requirements.txt
-
Configure environment:
cp .env.example .env
# Edit .env with your configuration
nano .env -
Generate a secure secret key:
python -c "import secrets; print(secrets.token_hex(32))"
# Copy the output to JWT_SECRET_KEY in .env -
Run the server:
python main.py
The server will start on
http://0.0.0.0:8080
by default.
First Run
On first run, the server automatically creates:
-
A default admin user:
admin/admin123 - The WebDAV root directory structure
- SQLite database with all required tables
⚠️ Important : Change the default admin password immediately!
Configuration
Environment Variables
All configuration is done through the
.env
file. Key settings:
Server Configuration
HOST=0.0.0.0 # Listen address
PORT=8080 # Listen port
SSL_ENABLED=false # Enable HTTPS
Authentication
AUTH_METHODS=basic,digest # Supported auth methods
JWT_SECRET_KEY=... # Secret for sessions (required)
SESSION_TIMEOUT=3600 # Session duration in seconds
WebDAV Settings
WEBDAV_ROOT=./webdav # Root directory for files
MAX_FILE_SIZE=104857600 # Max file size (100MB)
MAX_PROPFIND_DEPTH=3 # Depth limit for PROPFIND
LOCK_TIMEOUT_DEFAULT=3600 # Default lock timeout
See
.env.example
for complete configuration options.
Usage
Connecting with Windows Explorer
Method 1: Map Network Drive
- Open File Explorer
- Right-click "This PC" → "Map network drive"
- Choose a drive letter
-
Enter the WebDAV URL:
http://your-server:8080/
- Check "Connect using different credentials"
- Enter your username and password
- Click "Finish"
Method 2: Add Network Location
- Open File Explorer
- Right-click "This PC" → "Add a network location"
- Choose "Choose a custom network location"
-
Enter the WebDAV URL:
http://your-server:8080/
- Enter credentials when prompted
Windows with SSL (Recommended)
For HTTPS connections:
Note : Windows requires port 443 for HTTPS WebDAV by default. To use other ports, modify Windows registry:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters
BasicAuthLevel = 2
Connecting with macOS Finder
- Open Finder
-
Press
Cmd+K(Go → Connect to Server) -
Enter the WebDAV URL:
http://your-server:8080/
- Click "Connect"
- Enter your credentials
Connecting with Linux (davfs2)
-
Install davfs2:
sudo apt-get install davfs2 # Ubuntu/Debian
-
Mount the WebDAV share:
sudo mount -t davfs http://your-server:8080/ /mnt/webdav
-
Enter credentials when prompted
Command Line Tools
Using curl
Upload a file:
curl -u username:password -T file.txt http://localhost:8080/file.txt
Download a file:
curl -u username:password http://localhost:8080/file.txt -o file.txt
Create a directory:
curl -u username:password -X MKCOL http://localhost:8080/newdir/
Delete a resource:
curl -u username:password -X DELETE http://localhost:8080/file.txt
Using cadaver
cadaver http://localhost:8080/
# Enter credentials
dav:/> ls
dav:/> put localfile.txt
dav:/> get remotefile.txt
dav:/> mkcol newfolder
User Management
Creating Users Programmatically
import asyncio
from main import Database
async def create_user():
db = Database('./webdav.db')
user_id = await db.create_user(
username='john',
password='SecurePass123!',
email='john@example.com',
role='user'
)
print(f"Created user with ID: {user_id}")
asyncio.run(create_user())
User Roles
- admin : Full access to all features and user management
- user : Standard user with access to their own directory
- readonly : Read-only access (future implementation)
Directory Structure
webdav/
├── users/
│ ├── admin/ # Admin user directory
│ ├── john/ # John's private directory
│ └── jane/ # Jane's private directory
├── shared/ # Shared between all users (optional)
└── public/ # Public access (optional)
Production Deployment
Using Gunicorn
Create
gunicorn_config.py
:
import multiprocessing
bind = "0.0.0.0:8080"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "aiohttp.GunicornWebWorker"
keepalive = 30
timeout = 60
accesslog = "./logs/access.log"
errorlog = "./logs/error.log"
loglevel = "info"
Run with Gunicorn:
gunicorn main:init_app --config gunicorn_config.py
Using Docker
Create
Dockerfile
:
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Create directories
RUN mkdir -p /app/webdav /app/logs
# Expose port
EXPOSE 8080
# Run application
CMD ["python", "main.py"]
Create
docker-compose.yml
:
version: '3.8'
services:
webdav:
build: .
ports:
- "8080:8080"
volumes:
- ./webdav:/app/webdav
- ./logs:/app/logs
- ./webdav.db:/app/webdav.db
environment:
- HOST=0.0.0.0
- PORT=8080
- DB_PATH=/app/webdav.db
- WEBDAV_ROOT=/app/webdav
restart: unless-stopped
# Optional: Redis for caching
redis:
image: redis:alpine
ports:
- "6379:6379"
restart: unless-stopped
Build and run:
docker-compose up -d
Nginx Reverse Proxy
Create
/etc/nginx/sites-available/webdav
:
server {
listen 80;
server_name webdav.example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name webdav.example.com;
ssl_certificate /etc/letsencrypt/live/webdav.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webdav.example.com/privkey.pem;
# WebDAV specific settings
client_max_body_size 100M;
client_body_buffer_size 128k;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebDAV headers
proxy_set_header Destination $http_destination;
proxy_set_header Depth $http_depth;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Enable and reload:
sudo ln -s /etc/nginx/sites-available/webdav /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Security Best Practices
SSL/TLS Configuration
- Always use HTTPS in production
-
Generate SSL certificates with Let's Encrypt:
sudo certbot certonly --nginx -d webdav.example.com
-
Update
.env:SSL_ENABLED=true
SSL_CERT_PATH=/etc/letsencrypt/live/webdav.example.com/fullchain.pem
SSL_KEY_PATH=/etc/letsencrypt/live/webdav.example.com/privkey.pem
Authentication
- Use Digest authentication over Basic when possible
- Enforce strong passwords (min length, complexity)
- Enable rate limiting to prevent brute force attacks
- Implement account lockout after failed attempts
Firewall Rules
# Allow only necessary ports
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Regular Updates
# Update dependencies
pip install --upgrade -r requirements.txt
# Backup database before updates
cp webdav.db webdav.db.backup
Monitoring and Logging
Log Files
Logs are stored in
./logs/
directory:
-
webdav.log- Application logs -
access.log- Access logs (requests) -
error.log- Error logs
Log Format
JSON-structured logs for easy parsing:
{
"timestamp": "2025-01-15T10:30:45Z",
"level": "INFO",
"user": "john",
"method": "PROPFIND",
"path": "/documents/",
"status": 207,
"duration_ms": 45
}
Health Check
Access the health check endpoint:
curl http://localhost:8080/health
Troubleshooting
Windows Explorer Connection Issues
Problem : "The network folder is no longer available"
Solutions :
-
Increase Windows WebClient timeout:
Registry: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters
FileSizeLimitInBytes = DWORD: 0xFFFFFFFF -
Enable Basic Authentication over HTTP (insecure - use only for testing):
Registry: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters
BasicAuthLevel = 2 -
Restart WebClient service:
net stop webclient
net start webclient
Problem : "The specified server cannot perform the requested operation"
Solution : Ensure the URL ends with a slash and doesn't include port 80:
-
❌
http://server:80/path -
✅
http://server/path/
macOS Finder Issues
Problem : Connection refused or timeout
Solutions :
-
Use HTTP URL with explicit protocol:
http://server:8080/
-
Check firewall settings on server
-
Try connecting via IP address instead of hostname
Performance Issues
Problem : Slow PROPFIND responses
Solutions :
-
Reduce
MAX_PROPFIND_DEPTHin.env:MAX_PROPFIND_DEPTH=1
-
Enable caching with Redis:
CACHE_ENABLED=true
REDIS_HOST=localhost -
Increase worker processes:
WORKERS=8
Database Locked Errors
Problem : "database is locked" errors
Solutions :
- Enable WAL mode (automatic in code)
- Ensure only one process accesses the database
- Use separate databases for multiple instances
API Documentation
WebDAV Methods
PROPFIND - Property Discovery
PROPFIND /documents/ HTTP/1.1
Depth: 1
Content-Type: application/xml
<D:propfind xmlns:D="DAV:">
PROPPATCH - Property Modification
PROPPATCH /file.txt HTTP/1.1
Content-Type: application/xml
<D:propertyupdate xmlns:D="DAV:">
My Document
LOCK - Resource Locking
LOCK /file.txt HTTP/1.1
Timeout: Second-3600
Content-Type: application/xml
<D:lockinfo xmlns:D="DAV:">
mailto:john@example.com
Development
Running Tests
# Install test dependencies
pip install pytest pytest-asyncio pytest-aiohttp pytest-cov
# Run tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=. --cov-report=html
Code Style
# Format code
black main.py
# Lint code
flake8 main.py
# Type checking
mypy main.py
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
MIT License - See LICENSE file for details
Support
For issues, questions, or contributions:
- GitHub Issues: [Create an issue]
- Documentation: [Wiki]
- Community: [Discussions]
Changelog
Version 1.0.0 (2025-01-15)
- Initial release
- Full RFC 4918 compliance
- Windows Explorer compatibility
- Multi-user support with SQLite backend
- Basic and Digest authentication
- Resource locking
- Custom properties
- Production-ready with Gunicorn support
Acknowledgments
- RFC 4918 - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)
- aiohttp - Async HTTP client/server framework
- Python community
Made with ❤️ for the WebDAV community