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:
https://your-server:8443/
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 logsaccess.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
<?xml version="1.0"?>
<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>
PROPPATCH - Property Modification
PROPPATCH /file.txt HTTP/1.1
Content-Type: application/xml
<?xml version="1.0"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<D:displayname>My Document</D:displayname>
</D:prop>
</D:set>
</D:propertyupdate>
LOCK - Resource Locking
LOCK /file.txt HTTP/1.1
Timeout: Second-3600
Content-Type: application/xml
<?xml version="1.0"?>
<D:lockinfo xmlns:D="DAV:">
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
<D:owner>
<D:href>mailto:john@example.com</D:href>
</D:owner>
</D:lockinfo>
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
| .env.example | |
| docker-compose.yml | |
| Dockerfile | |
| gunicorn_config.py | |
| main.py | |
| nginx.conf | |
| QUICKSTART.md | |
| README.md | |
| requirements.txt | |
| setup.sh | |
| test_webdav.py | |
| webdav_cli.py | |
| webdav.db | |
| webdav.service |