A from-scratch WebDAV server implementation with configurable authentication to understand how the protocol works.
I used AI to produce the base code and have been modifying it for my use case. Feel free to use to play around with it. I'll likely be making changes to the project as I play around with this for my home lab.
Check the Quickstart Guide in the Wiki to see how it works.
- Username:
admin - Password:
password
Change these immediately using the user management commands!
The server uses config.json for settings. It's created automatically on first run.
{
"RequireAuthentication": true,
"ListenPrefix": "http://localhost:8080/",
"StoragePath": "./webdav-storage",
"UsersFile": "users.json"
}| Setting | Description | Default | Notes |
|---|---|---|---|
RequireAuthentication |
Enable/disable authentication | true |
|
ListenPrefix |
URL to listen on | http://localhost:8080/ |
See binding notes below |
StoragePath |
Directory for file storage | ./webdav-storage |
|
UsersFile |
Path to users database | users.json |
http://localhost:8080/- Default, may bind to IPv6 only on Linuxhttp://127.0.0.1:8080/- IPv4 localhost only (recommended for local access)http://+:8080/- All interfaces (IPv4 and IPv6) - requires sudo on Linuxhttp://0.0.0.0:8080/- NOT supported on Linux (use+instead)
View current configuration:
dotnet run -- configChange settings:
# Enable/disable authentication
dotnet run -- config auth true
dotnet run -- config auth false
# Change listen address - Local IPv4 only
dotnet run -- config prefix http://127.0.0.1:8080/
# Change listen address - All interfaces (requires sudo)
dotnet run -- config prefix http://+:8080/
# Change storage directory
dotnet run -- config storage /var/webdav-data
# Change users file location
dotnet run -- config usersfile /etc/webdav/users.jsonIf you set RequireAuthentication to false:
- Anyone can access, upload, delete, and modify your files!
- Only use this for:
- Local development/testing
- Behind a firewall
- When you have other security measures in place (VPN, firewall rules, etc.)
- NEVER expose an unauthenticated server to the internet!
All clients will prompt for username and password when connecting.
- Open File Explorer
- Right-click "This PC" → "Add a network location"
- Enter:
http://localhost:8080/ - Enter your username and password when prompted
- Check "Remember my credentials" for convenience
- Finder → Go → Connect to Server (⌘K)
- Enter:
http://username:password@localhost:8080/- Or:
http://localhost:8080/and enter credentials when prompted
- Or:
# Mount with davfs2
sudo mount -t davfs http://localhost:8080/ /mnt/webdav
# Enter credentials when promptedSimply connect without providing credentials:
- Windows:
http://localhost:8080/ - macOS:
http://localhost:8080/ - Linux:
http://localhost:8080/
If you experience "connection refused" errors on Linux:
-
Configure the server for IPv4:
dotnet run -- config prefix http://127.0.0.1:8080/ dotnet run
-
In WebDAV settings, use:
- WebDAV URL:
http://127.0.0.1:8080 - Username:
admin - Password: your password
- WebDAV URL:
Note: Use 127.0.0.1 (explicit IPv4) instead of localhost to avoid IPv6 binding issues.
To access from other devices (phones, tablets, other computers):
-
Configure for all interfaces:
dotnet run -- config prefix http://+:8080/ sudo dotnet run # Requires elevated privileges -
Find your server's IP address:
hostname -I # OR ip addr show | grep "inet "
-
Connect using:
- URL:
http://YOUR_IP_ADDRESS:8080 - Username: your username
- Password: your password
- URL:
Example: http://192.168.1.100:8080
-
Client makes request without credentials
GET / HTTP/1.1 -
Server responds with 401 Unauthorized (if auth required)
HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="WebDAV Server" -
Client sends credentials
GET / HTTP/1.1 Authorization: Basic YWRtaW46cGFzc3dvcmQ=The value is Base64-encoded
username:password -
Server validates and responds
- Valid: Processes request normally
- Invalid: Returns 401 again
- Auth disabled: Processes request without checking credentials
-
Password Hashing
- Passwords are hashed using SHA256
- Never stored in plain text
- Hash:
Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(password)))
-
Persistent Storage
- Users stored in
users.json(or configured file) - Format:
{ "admin": "XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=", "john": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8" } - Users stored in
-
Request Validation
- Every request checks the
Authorizationheader (when auth enabled) - Invalid/missing credentials → 401 response
- The
WWW-Authenticateheader tells clients what auth method to use
- Every request checks the
ServerConfig.cs:
public class ServerConfig
{
public bool RequireAuthentication { get; set; } = true;
// ... other settings
}WebDavServer.cs:
// Check authentication at start of every request
if (_requireAuth && !_authManager.ValidateCredentials(request.Headers["Authorization"]))
{
response.StatusCode = 401;
response.AddHeader("WWW-Authenticate", "Basic realm=\"WebDAV Server\"");
return;
}# Upload a file
curl -u admin:password -T myfile.txt http://localhost:8080/
# Download a file
curl -u admin:password http://localhost:8080/myfile.txt
# List directory
curl -u admin:password -X PROPFIND http://localhost:8080/ -H "Depth: 1"# Upload a file
curl -T myfile.txt http://localhost:8080/
# Download a file
curl http://localhost:8080/myfile.txt
# List directory
curl -X PROPFIND http://localhost:8080/ -H "Depth: 1"WebDAV extends HTTP with these additional methods:
Retrieves properties (metadata) about resources.
Request Headers:
Depth: 0- Only the resource itselfDepth: 1- Resource and its immediate childrenDepth: infinity- Resource and all descendants (rarely used)
Creates a new directory (collection in WebDAV terminology).
Copies a file or directory to a new location.
Moves a file or directory.
Deletes a file or directory.
Creates or updates a file.
Retrieves file content.
Returns what the server supports.
Basic Authentication sends credentials in Base64, which is NOT encryption! Anyone intercepting the traffic can decode the credentials.
You MUST use HTTPS/TLS when exposing to the internet!
-
Always Enable Authentication in Production
dotnet run -- config auth true -
Use a Reverse Proxy (Nginx, Caddy, or Apache)
server { listen 443 ssl http2; server_name webdav.yourdomain.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; 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; # Increase timeout for large file uploads proxy_read_timeout 300s; proxy_send_timeout 300s; client_max_body_size 0; } }
-
Get a Free SSL Certificate from Let's Encrypt
# Using Certbot sudo certbot --nginx -d webdav.yourdomain.com -
Firewall Configuration
# Only allow HTTPS sudo ufw allow 443/tcp # Block HTTP if not needed sudo ufw deny 80/tcp # Allow only from specific IP (optional) sudo ufw allow from 192.168.1.0/24 to any port 8080
-
Run as a System Service (systemd example)
[Unit] Description=WebDAV Server After=network.target [Service] Type=simple User=webdav WorkingDirectory=/opt/webdav ExecStart=/usr/bin/dotnet /opt/webdav/WebDavServer.dll Restart=always RestartSec=10 # Security hardening NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/opt/webdav/webdav-storage [Install] WantedBy=multi-user.target
-
Additional Security Measures
- Use strong passwords (minimum 16 characters, mixed case, numbers, symbols)
- Implement rate limiting (via reverse proxy)
- Add IP whitelist if possible
- Regular security updates
- Monitor access logs
- Consider fail2ban to block brute force attempts
- Backup regularly
# Limit to 10 requests per second per IP
limit_req_zone $binary_remote_addr zone=webdav:10m rate=10r/s;
server {
location / {
limit_req zone=webdav burst=20 nodelay;
# ... proxy settings
}
}dotnet run -- config auth false
dotnet runGreat for local testing where you want quick access without credentials.
dotnet run -- config auth true
dotnet run -- config prefix http://+:8080/
sudo dotnet runAccess from other devices on your home network with password protection.
dotnet run -- config auth true
dotnet run -- config prefix http://127.0.0.1:8080/
# Set up Nginx reverse proxy with SSL
# Configure firewall to only allow HTTPSFull security for public internet access.
Issue: Connection refused (ECONNREFUSED) on Linux
- Cause: Server bound to IPv6, client trying IPv4
- Solution: Use
dotnet run -- config prefix http://127.0.0.1:8080/for IPv4 - Alternative: Use
http://127.0.0.1:8080(notlocalhost) in your client
Issue: "The request is not supported" error when using 0.0.0.0
- Cause: HttpListener on Linux doesn't support
0.0.0.0binding - Solution: Use
http://+:8080/instead for all interfaces
Issue: Can access without password even though auth is enabled
- Check
config.json- ensureRequireAuthenticationistrue - Restart the server after changing config
- Verify with:
dotnet run -- config
Issue: "Authentication required" but credentials are correct
- Check the
users.jsonfile exists - Try removing and re-adding the user:
dotnet run -- adduser username password - Ensure no typos in username/password
Issue: Windows keeps asking for credentials
- Make sure "Remember my credentials" is checked
- Try:
cmdkey /add:localhost /user:admin /pass:password
Issue: curl shows "401 Unauthorized"
- Verify you're using
-u username:password - Check for typos in credentials
- Confirm auth is enabled:
dotnet run -- config
Issue: Can't connect from other devices
- Check if
ListenPrefixis set to0.0.0.0instead oflocalhost - Update:
dotnet run -- config prefix http://0.0.0.0:8080/ - Verify firewall allows connections on port 8080
Issue: Files being saved to a folder called "config" or "adduser"
- You forgot the
--! Use:dotnet run -- config auth true - Without
--, dotnet interprets arguments incorrectly - Delete the wrongly created folders and use the correct syntax
┌──────────────┐
│ Client │
│ (File Mgr, │
│ Browser, │
│ curl, etc) │
└──────┬───────┘
│ HTTP Request + Authorization Header (if auth enabled)
│
┌──────▼────────────────────────────────────┐
│ WebDavServer.cs │
│ ┌────────────────────────────────────┐ │
│ │ 1. Check Configuration │ │
│ │ - Is auth required? │ │
│ └─────────────────┬──────────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Auth Required? │ │
│ └──┬───────────────┬──┘ │
│ YES NO │
│ │ │ │
│ ┌─────────▼─────────┐ │ │
│ │ Validate Creds │ │ │
│ │ with AuthManager │ │ │
│ └─────────┬─────────┘ │ │
│ Valid│ Invalid │ │
│ │ │ │ │
│ │ ┌──▼──────┐ │ │
│ │ │ 401 │ │ │
│ │ │ Return │ │ │
│ │ └─────────┘ │ │
│ │ │ │
│ ┌─────────▼───────────────▼───────────┐ │
│ │ 2. Route Request │ │
│ │ - GET, PUT, DELETE, MKCOL... │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────────▼───────────────────┐ │
│ │ 3. File System Operations │ │
│ │ - Read/Write files │ │
│ │ - Create/Delete directories │ │
│ └─────────────────────────────────────┘ │
└───────────────────────────────────────────┘
│
┌──────▼────────────────────┐
│ ServerConfig.cs │
│ ┌─────────────────────┐ │
│ │ config.json │ │
│ │ { │ │
│ │ "RequireAuth": │ │
│ │ true/false │ │
│ │ } │ │
│ └─────────────────────┘ │
└───────────────────────────┘
│
┌──────▼────────────────────┐
│ AuthenticationManager │
│ ┌─────────────────────┐ │
│ │ users.json │ │
│ │ { │ │
│ │ "admin": "hash" │ │
│ │ } │ │
│ └─────────────────────┘ │
└───────────────────────────┘
This is a learning implementation. Production servers might add:
- Digest Authentication (more secure than Basic)
- OAuth/Bearer Tokens
- Resource Locking (LOCK/UNLOCK methods)
- ETags for caching
- Quota Management (per-user storage limits)
- Access Control Lists (per-user/per-directory permissions)
- Audit Logging (track who accessed what)
- Multi-factor Authentication
- Session Management
- CORS Headers (for web clients)