A self-hosted WebDAV server for synchronizing your Obsidian vaults between mulltiple devices. Includes automatic HTTPS certificate provisioning, version control, and backup functionality. Works with Obsidian desktop and mobile clients using the Remotely Save community plugin.
- Introduction
- Prerequisites
- Installation & Setup
- Obsidian Configuration
- Testing & Validation
- Verifying Sync is Working
- Maintenance Commands
- Git Operations & GitHub
- Troubleshooting
- Architecture
This project runs a self-hosted WebDAV server for syncing your Obsidian vaults inside a Docker container. All your notes and files are also automatically committed to a local Git repository on the server, providing advanced version history and recovery options.
| Technology | Role | Why |
|---|---|---|
| Caddy | Web server & reverse proxy | Automatic HTTPS via Let's Encrypt with zero manual cert management; minimal, readable configuration syntax. |
| caddy-webdav plugin | WebDAV protocol support | The mholt/caddy-webdav plugin for Caddy adds PROPFIND, LOCK, UNLOCK, and other methods that the Remotely Save plug-in for Obsidian requires. |
| Docker & docker-compose | Containerization & orchestration | Reproducible isolated environment; easy restart policy (unless-stopped); built-in log rotation limits. |
| Git | Version control & backup | Automatic timestamped commit history of all vault contents; enables rollback to any point in time; optional integration with GitHub/GitLab for off-site backup. |
| Alpine Linux | Container base OS | Minimal footprint; only essential packages installed (git, bash, coreutils). Well-known base image often used in small server setups. |
- OS: Debian/Ubuntu (or any Linux with Docker support)
- Disk: Minimum 1 GB free space; but to be scaled based on vault requirements
- CPU: Any (minimal resource usage)
- RAM: 256 MB minimum; 512 MB recommended
- Docker (v20+):
docker --version - Docker Compose (v2+):
docker compose --version - Git:
git --version - curl:
curl --version
Installation:
# Debian/Ubuntu
sudo apt update && sudo apt install -y docker.io docker-compose git curl
sudo usermod -aG docker $(whoami) # Allow non-root docker commands- DNS A record: Point your domain (e.g.,
obisian.your-domain.xyz) to your server's public IP. The DNS record MUST be setup before installing Caddy, in order for automatic HTTPS certificate provisioning (Let's Encrypt ACME challenge) to work. - Firewall: Ports 80 and 443 must be open (needed for Let's Encrypt ACME challenge and WebDAV access).
- Internet: Outbound HTTPS to
acme-v02.api.letsencrypt.orgfor certificate provisioning.
Before proceeding with the installation, check the prerequisites above. A valid (sub-)domain with proper DNS records resolving to your server's IP address is a hard requirement. The automatic certificate provisioning will fail otherwise.
git clone https://github.com/sevba/obsidian-webdav-server.git
cd obsidian-webdav-serverThis repository contains all the necessary files:
Dockerfileβ Custom Caddy build with WebDAV plugin + Git supportdocker-compose.ymlβ Container orchestration configCaddyfileβ Caddy web server configuration (template)git-auto-commit.shβ Automatic Git commit script
Create a bcrypt-hashed password for WebDAV authentication:
# Using Caddy (if installed locally)
caddy hash-password
# Or use the temporary container method
docker run --rm -it caddy:latest caddy hash-passwordSave the output hash for Step 3.
Edit the Caddyfile and replace the following placeholders:
YOUR_DOMAINβ Your server's domain (e.g.,obisian.your-domain.xyz)YOUR_HASHED_PASSWORDβ The bcrypt hash generated in Step 2obsidianβ Change to your desired WebDAV username (if needed)
# Open the file in your editor
nano Caddyfile
# or
vim CaddyfileExample section to update:
YOUR_DOMAIN {
...
basicauth {
obsidian YOUR_HASHED_PASSWORD
}
...
}Before starting the container, ensure DNS is correctly configured:
nslookup YOUR_DOMAIN
dig YOUR_DOMAIN +shortShould return your server's public IP address. DNS must be set up BEFORE starting Caddy, otherwise Let's Encrypt certificate provisioning will fail.
docker compose build
docker compose up -dCheck that the container starts successfully:
docker compose psExpected output: Container status should show Up and healthy after 40 seconds (certificate provisioning takes time on first startup).
- In Obsidian, go to Settings β Community Plugins
- Search for "Remotely Save" and install
- Enable the plugin
- Open the Remotely Save plugin settings
- Choose WebDAV as the sync service
- Fill in the following:
| Field | Value |
|---|---|
| URL | https://YOUR_DOMAIN/obsidian/ |
| Username | obsidian |
| Password | (your plaintext password) |
| Schedule For Auto Run | Enabled (recommended) |
| Run Once on Startup | Enabled (recommended) |
| Sync on Save | Enabled (recommended) |
curl https://YOUR_DOMAIN/health
# Expected: 200 OK
# Response: OKcurl -I https://YOUR_DOMAIN/obsidian/
# Expected: 401 Unauthorizedcurl -u obsidian:your_password https://YOUR_DOMAIN/obsidian/
# Expected: 200 OK or 207 Multi-Status
# (207 is common for empty WebDAV directories)curl -v https://YOUR_DOMAIN/health 2>&1 | grep -i certificate
# Should show a valid Let's Encrypt certificatedocker compose ps
# Container should show status: "Up X seconds (healthy)"docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
# Should show success messages (not errors)After syncing from Obsidian using Remotely Save:
docker exec obsidian-caddy ls -la /data/webdav/You should see your vault structure (folders and notes).
docker exec obsidian-caddy git -C /data/webdav log --oneline -10
# Expected output:
# abc1234 Auto-commit: 2026-02-17 09:42:00
# def5678 Auto-commit: 2026-02-17 09:41:00
# ...docker compose logs caddy | grep "\[git-auto-commit\]"
# Expected output:
# [git-auto-commit] 2026-02-17 09:42:00 Committed 3 file(s)Below are useful but optional commands to view and manage your WebDAV server.
# Follow logs (live)
docker compose logs -f caddy
# View last 50 lines
docker compose logs --tail=50 caddy# Follow in real-time
docker exec obsidian-caddy tail -f /data/logs/access.log
# View last 20 requests
docker exec obsidian-caddy tail -20 /data/logs/access.log# Start
docker compose up -d
# Stop
docker compose down
# Restart (preserves volumes)
docker compose restartUpdate Caddyfile and reload:
docker compose exec caddy caddy reload --config /etc/caddy/CaddyfileNo container restart needed.
Create a compressed archive of your vault (for copying to off-site backup server). This will back up the complete Git repository.
docker run --rm \
-v obsidian-webdav-server_caddy_data:/data \
-v ~/backups:/backup \
alpine tar czf /backup/obsidian_backup.tar.gz /data/webdav/
# Verify backup
tar tzf obsidian_backup.tar.gz | head -20Below are useful but optional commands to view and manage Git version history.
# List commits with one-line messages
docker exec obsidian-caddy git -C /data/webdav log --oneline -20
# Show detailed commit with diff
docker exec obsidian-caddy git -C /data/webdav show <commit-hash>
# Show files changed in a commit
docker exec obsidian-caddy git -C /data/webdav show --stat <commit-hash># Changes between HEAD and previous commit
docker exec obsidian-caddy git -C /data/webdav diff HEAD~1
# All uncommitted changes
docker exec obsidian-caddy git -C /data/webdav diff
# Staged changes only
docker exec obsidian-caddy git -C /data/webdav diff --cachedRestore a file to a previous version:
# Find the commit hash
docker exec obsidian-caddy git -C /data/webdav log --oneline path/to/note.md
# Restore the file
docker exec obsidian-caddy git -C /data/webdav checkout <commit-hash> -- path/to/note.md
# Verify restoration
docker exec obsidian-caddy git -C /data/webdav diff HEADThe restored file's timestamp will be set to the moment you execute the commend, so Remotely Save will consider this version to be the latest and will download it from the server to your local Obsidian client (as long as the Action For Conflict setting of Remotely Save is set to "newer version survives" which is the default setting).
WARNING: This discards all changes since the target commit.
# Find the target commit
docker exec obsidian-caddy git -C /data/webdav log --oneline
# Reset to that commit (discards all changes after)
docker exec obsidian-caddy git -C /data/webdav reset --hard <commit-hash>
# Verify
docker exec obsidian-caddy git -C /data/webdav log --oneline -5Below are useful but optional commands to sync your Git repository to a remote Git server. This can be considered an "off-site backup" and offers additional protection against dataloss in case of hard drive failure. WARNING: Make sure you trust the remote Git server and ensure privacy settings (e.g. private repository) are configured accordingly. All your Obisian data will be pushed to this remote repository. The examples below use Github as remote server, however it is recommended not to store any sensitive data in a Github repository regardless of it being private or not. For sensitive data, make sure you fully trust your remote Git service provider.
Configure Git inside container:
# Use a GitHub token in the remote URL (single command)
docker exec obsidian-caddy git -C /data/webdav remote add origin https://TOKEN@github.com/YOUR_USERNAME/YOUR_REPO.git
# Then git will prompt for password once (enter the token)
docker exec obsidian-caddy git -C /data/webdav push -u origin mainPush to remote server:
# Verify remote is configured
docker exec obsidian-caddy git -C /data/webdav remote -v
# Push all commits
docker exec obsidian-caddy git -C /data/webdav push -u origin main
# Subsequent pushes (after auto-commits)
docker exec obsidian-caddy git -C /data/webdav pushAutomate pushes:
Add a cron job or modify git-auto-commit.sh to push after each commit:
docker exec obsidian-caddy git -C /data/webdav push origin mainCheck logs:
docker compose logs caddyCommon causes:
- Port already in use:
docker ps -ato find conflicting container- Solution: Change port mapping in
docker-compose.yml(e.g.,8443:443) - Important: Note that Caddy requires port 80 to be open in order to receive the ACME challenges for provisioning HTTPS certificates. I highly recommend the use of ports 80 and 443 for your WebDAV server. If these ports are not available, consider requesting your hosting provider to provision an additional public IP address.
- Solution: Change port mapping in
- DNS not resolving:
nslookup YOUR_DOMAINreturns no result- Solution: Wait for DNS propagation (up to 24 hours) or update DNS record
- File permissions: Cannot write to
caddy_datavolume- Solution: Check volume ownership:
docker exec obsidian-caddy ls -ld /data
- Solution: Check volume ownership:
Validate Caddyfile syntax:
docker compose exec caddy caddy validate --config /etc/caddy/CaddyfileVerify order webdav before file_server is present at the top of the config.
docker exec obsidian-caddy cat /etc/caddy/Caddyfile | head -20Check Let's Encrypt logs:
docker compose logs caddy | grep -i "acme\|certificate\|let"Common causes:
- DNS not propagated: Domain doesn't resolve to server IP yet (wait up to 24 hours)
- Port 80 blocked by firewall: Let's Encrypt uses port 80 for ACME challenge
- Solution: Open port 80 in firewall, or configure ACME via DNS challenge (requires Caddyfile modification)
- Rate limited: Let's Encrypt has limits (50 failures/domain/hour)
- Solution: Wait 1 hour before retrying; use staging environment for testing
Manual trigger (if needed):
docker compose exec caddy caddy reload --config /etc/caddy/CaddyfileCheck credentials:
curl -u obsidian:your_password https://YOUR_DOMAIN/obsidian/Should return 200 or 207, not 401.
Verify URL format:
- URL must end with
/:https://YOUR_DOMAIN/obsidian/ - Check that
YOUR_DOMAINmatches DNS and certificate
Check Caddy health:
curl https://YOUR_DOMAIN/health
# Expected: "OK" 200Check git-auto-commit script is running:
docker compose logs caddy | grep "\[git-auto-commit\]" | tail -5Verify Git repo exists:
docker exec obsidian-caddy git -C /data/webdav status
# Should show branch, commit count, etc. (not "fatal")Manually trigger a commit:
docker exec obsidian-caddy bash -c 'cd /data/webdav && git add -A && git commit -m "Manual commit test"'Find conflicting container:
docker ps -a
lsof -i :443 # (if lsof installed on host)
netstat -tuln | grep 443- Caddy Documentation: https://caddyserver.com/docs/
- caddy-webdav Plugin: https://github.com/mholt/caddy-webdav
- Obsidian Remotely Save: https://github.com/remotely-save/remotely-save
Obsidian Client
β (HTTPS WebDAV)
obisian.your-domain.xyz:443
β
Caddy Server
ββ /obsidian/* β [Basic Auth] β WebDAV handler β /data/webdav/
ββ /health β "OK" 200
β
Git Auto-Commit Watcher (background process)
ββ Every 30 seconds: check for changes
ββ Skip if files modified within last 30 seconds (sync is still ongoing)
/data/
βββ webdav/ β Your vault files + .git/ repo
β βββ folder1/
β βββ note.md
β βββ .git/ β Git history (managed by git-auto-commit.sh)
βββ logs/
β βββ access.log β JSON formatted request log (rotated)
βββ caddy/
βββ certificates/ β Let's Encrypt TLS certs
βββ autosave.json β Caddy config backup
The git-auto-commit.sh script runs continuously in the background:
- Startup: Initializes a Git repo in
/data/webdav/if one doesn't exist - Loop (every 30 seconds):
- Checks for files modified in the last 30 seconds (ignores if active syncs detected to prevent committing partially written files)
- If no recent activity, stages all changes (
git add -A) - Commits with a timestamped message:
Auto-commit: 2026-02-17 09:42:00 - Updates reference timestamp for next cycle
Key Parameters:
WATCH_INTERVAL=30β Check for changes every 30 seconds
Last Updated: 2026-02-18