config.yaml is meant to hold real, deployment-specific secrets (bcrypt password hashes) and the public base_url, so it shouldn't be committed. Add it to .gitignore, remove it from version control (kept locally on disk), and check in config.example.yaml as the template to copy from. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DAV Server
A self-hosted CalDAV, CardDAV, and WebDAV server written in Go.
Features
| Protocol | Use case |
|---|---|
| CalDAV | Calendars — sync with Apple Calendar, Thunderbird, GNOME Calendar, … |
| CardDAV | Contacts — sync with Apple Contacts, GNOME Contacts, … |
| WebDAV | General file access via Finder, Windows Explorer, Nautilus, … |
- HTTP Basic Auth with bcrypt password storage
- Per-user isolated collections
- Auto-discovery via
/.well-known/caldavand/.well-known/carddav - Optional TLS (or use a reverse proxy)
- Structured logging (text or JSON)
- Graceful shutdown
- Docker & Docker Compose support
Quick start
1. Install dependencies
go mod tidy
2. Generate password hashes
go run ./tools/hashpwd mysecretpassword
# Outputs: $2b$12$...
3. Create your config.yaml
Copy the example config and edit it — config.yaml is git-ignored so your
real credentials/domain never get committed:
cp config.example.yaml config.yaml
Replace the placeholder hashes with your real bcrypt hashes:
users:
alice:
password: "$2b$12$<hash generated above>"
display_name: "Alice Smith"
email: "alice@example.com"
calendars:
- personal
- work
address_books:
- contacts
4. Run the server
make run
# or
go run ./cmd/server -config config.yaml
The server starts at http://localhost:8080.
Docker
# Build and start
docker compose up --build
# Or build manually
docker build -t davserver .
docker run -p 8080:8080 \
-v ./config.yaml:/app/config.yaml:ro \
-v dav-data:/app/data \
davserver
Client configuration
Apple Calendar / Contacts (macOS / iOS)
- Go to Settings → Calendar → Accounts → Add Account → Other → Add CalDAV Account
- Enter:
- Server:
http://yourserver:8080 - Username:
alice - Password: your plaintext password
- Server:
- The app will auto-discover calendars at
/cal/.
Same flow for CardDAV with Contacts app.
Thunderbird
- Install the TbSync add-on + CalDAV & CardDAV provider
- Add a new account and point it at
http://yourserver:8080/.well-known/caldav
GNOME Calendar / Evolution
Use the GNOME Online Accounts panel:
- Server:
http://yourserver:8080 - Check CalDAV / CardDAV as appropriate
API endpoints
| Path | Description |
|---|---|
/.well-known/caldav |
Redirects to /cal/ |
/.well-known/carddav |
Redirects to /card/ |
/cal/ |
CalDAV principal (same URL for every user; resolved via Basic Auth) |
/cal/home/ |
Calendar home-set (lists the user's calendars) |
/cal/home/<calendar>/ |
Calendar collection |
/card/ |
CardDAV principal (same URL for every user; resolved via Basic Auth) |
/card/home/ |
Address book home-set (lists the user's address books) |
/card/home/<book>/ |
Address book collection |
/files/ |
WebDAV file storage (same URL for every user; resolved via Basic Auth) |
/healthz |
Health check (unauthenticated) |
Note: the
homesegment is a fixed literal (not a username or real resource) — it exists only to give the calendar/address-book home-set the path depth that the underlying CalDAV/CardDAV library expects when classifying resources by URL. Clients should never need to construct these URLs by hand; they're discovered automatically via.well-known+current-user-principal+calendar-home-set/addressbook-home-setproperties.
TLS / Reverse proxy
Self-signed certificate (development)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
Update config.yaml:
tls:
enabled: true
cert_file: cert.pem
key_file: key.pem
Caddy reverse proxy (recommended for production)
dav.example.com {
reverse_proxy localhost:8080
}
Configuration reference
server:
host: "0.0.0.0"
port: 8080
base_url: "https://dav.example.com" # used in DAV responses
auth:
realm: "My DAV Server"
storage:
data_dir: "./data" # all user data lives here
logging:
level: "info" # debug | info | warn | error
format: "text" # text | json
tls:
enabled: false
cert_file: ""
key_file: ""
users:
<username>:
password: "<bcrypt hash>"
display_name: "Full Name"
email: "user@example.com"
calendars: # pre-created calendar names
- personal
address_books: # pre-created address book names
- contacts
Project layout
caldav-server/
├── cmd/server/ # main entrypoint
├── internal/
│ ├── auth/ # HTTP Basic Auth middleware
│ ├── caldav/ # CalDAV backend
│ ├── carddav/ # CardDAV backend
│ ├── config/ # YAML config loader
│ ├── store/ # filesystem storage layer
│ └── webdav/ # WebDAV file handler
├── tools/hashpwd/ # bcrypt password hasher CLI
├── config.example.yaml # sample configuration (copy to config.yaml)
├── Dockerfile
├── docker-compose.yaml
└── Makefile
Running tests
make test
# or
go test ./... -race
Dependencies
| Package | Purpose |
|---|---|
github.com/emersion/go-webdav |
WebDAV/CalDAV/CardDAV protocol layer |
github.com/emersion/go-ical |
iCalendar parsing/serialisation |
github.com/emersion/go-vcard |
vCard parsing/serialisation |
golang.org/x/crypto |
bcrypt |
golang.org/x/net |
golang.org/x/net/webdav |
gopkg.in/yaml.v3 |
YAML config parsing |