Your calendar knows everything. Where you work. Who you meet. When you visit the doctor. What you do on weekends. And here is the problem: Google Calendar is not end-to-end encrypted (meaning Google itself can read every entry on its servers, even if no one else can). Google holds the keys. Anyone with a subpoena gets the keys. Anyone with a compromised Google account gets the keys. I am not a high-value target, but I am also not interested in making Google’s data hoarding easier than it has to be. So three months ago I moved my family calendar to a self-hosted Baikal server. Here is what worked, what did not, and what I would tell past me before doing this.
The Baikal pitch in one paragraph
Baikal is a lightweight CalDAV server (a standardized protocol for synchronizing calendar and contact data across devices, the same protocol Apple Calendar and Mozilla Thunderbird use to talk to their servers). It is a single PHP application that runs in Docker (a containerization tool that packages an app and all its dependencies into a portable unit) on a small Linux box. It supports multiple calendars, multiple users, and multiple devices per user. It does not have a web UI for editing events (you connect a client app, the same way you connect to Google Calendar through the Apple Calendar app or Mozilla Thunderbird). It does not have AI scheduling. It does not have meeting transcripts. It does exactly what a calendar is supposed to do and nothing else. For me, that is the appeal.
The Docker install took about 15 minutes. The hardest part was figuring out the right URL format for the CalDAV endpoint, which the docs get wrong in three different ways. The actual right URL is https://your-domain.example/dav/cal.php/calendars/yourusername/default. Get that wrong and every client app returns “authentication failed” with no hint as to why. I spent 90 minutes on that step alone. If you follow the recipe I have at the end of this article, you will skip all 90 minutes.
The clients that actually work
I tested four CalDAV clients across iOS, macOS, Android, and Linux. The cleanest setup is Thunderbird on desktop and DAVx5 on Android. On iOS, the built-in Calendar app supports CalDAV, but the sync is unreliable on cellular networks (it requires Wi-Fi to push new events to the server, which means invites you accept on your phone do not show up on your laptop until you are on the same network). The fix is to also install the paid Calendars app, which handles push sync correctly. Apple users have a known tax: you either pay Apple for the convenience or you pay the time to work around it.
Thunderbird is the best desktop client I have used for a self-hosted calendar. It supports multiple CalDAV accounts side by side, has clean reminders, and respects the local timezone settings without trying to be clever about it. I have one account for the family calendar and a second for my work calendar, and they show up as separate colors in the same view. The setup is a five-minute affair once you have the CalDAV URL.
DAVx5 on Android is the equivalent of Thunderbird. It syncs contacts and calendars in the background, handles push notifications when supported, and exposes the data to the built-in Android Calendar app and Contacts app. The free version supports two accounts. The paid version supports unlimited accounts and is about $6. I paid the $6. The free version would have been enough for me, but I wanted to support the developer.
The Apple Calendar app on macOS supports CalDAV out of the box, but the configuration UI is buried in System Settings under Internet Accounts, and the field labels are misleading. “Account Type: CalDAV” should say “CalDAV” but says “Other” in some macOS versions. Once configured, it works fine. Once you have done it twice, the setup is fast.
What broke and what I had to fix
The first problem was timezone handling. I created a “Family Trips” calendar in CEST (Central European Summer Time) and added an event for a flight in JST (Japan Standard Time). The event showed up on my phone in UTC, which was 7 hours off. The fix was to set the calendar’s default timezone in Baikal’s admin UI, not in the client app. Client timezone settings only affect how the client displays the time, not how the server stores it. If the server thinks the calendar is in UTC, every event is stored as UTC, and the client’s display logic has to guess what you meant. Set the server timezone, and the problem disappears.
The second problem was invite handling. Google Calendar lets you invite people by email and it sends a notification. Baikal does not. Baikal is just a CalDAV server. It does not know what an email is. If you want to invite people to events, you have to do it through the client app, which means the invite shows up as a regular iCal attachment (a .ics file, the universal calendar-event format) in their inbox. Most people can open that and add it to their calendar. Most people do not know they can. For my family, the workaround is to add the invite as a calendar entry directly in their CalDAV account, which I do from the admin UI. It is a 30-second task per event, but it is not zero.
Backups are the third problem. Google Calendar backs itself up to Google’s infrastructure. A self-hosted Baikal server is backed up to whatever I configure. I use restic (an open-source backup tool that takes deduplicated, encrypted snapshots) to a Backblaze B2 bucket, and I take a snapshot every night. The first time I tried to restore from backup, I learned that the Baikal data directory has a specific layout and restoring the wrong subdirectory leaves the calendars in a half-working state. The recipe below has the right subdirectory to back up.
What I would tell past me
Four things, in order of importance:
- Use a real domain name, not an IP address. The CalDAV clients all hard-code the protocol (
https://) and they will not accept a bare IP for security reasons. A free DuckDNS subdomain takes 5 minutes to set up and is good enough for a family of four. - Set the calendar timezone at creation, not later. If you create a calendar with the default (UTC) and then try to change it, every event you have already created stays in UTC. The server does not rewrite history. Create it in the right timezone from the start.
- Test the restore process on day one, not after the first data loss event. I tested it on day 30. If I had tested it on day 1, I would have caught a permissions issue in my restic config that I would not have caught during a real outage. Test your backups. Always.
- Buy the $6 DAVx5 paid version if you have more than two Android accounts. The free version supports two accounts and that is the limit. The paid version removes the cap and supports the developer who maintains the only Android client that handles CalDAV correctly.
Trade-offs
Baikal is not free in setup time. The full install is about 4 hours for someone who has done Linux server work before, and 2-3 days for someone who has not. The hardest part is not the Baikal install itself (15 minutes) but the supporting infrastructure: HTTPS certificate, reverse proxy (a server that sits in front of Baikal and handles the TLS encryption so Baikal does not have to), DNS record, backup automation, and the client setup on every device. If you are not comfortable with the Linux command line, this is not the right project.
Baikal is not free in maintenance. I spend about 15 minutes per month updating the Docker image, checking the backup logs, and verifying the SSL certificate is not about to expire. The calendar itself never breaks, but the surrounding plumbing needs occasional attention. If you do not enjoy this kind of work, you will resent it within six months.
Family friction is a real cost. My partner has to deal with a slightly less convenient client than Apple Calendar, and that is a real cost. The cost is small (one app switch and a manual setup), but it is non-zero, and it is a tax on someone who did not ask to be on a self-hosted calendar. I would not push this on a partner who is already skeptical of self-hosting.
If you want a calendar that syncs everywhere, that does not feed Google’s advertising machine, and that you control end to end, Baikal is the right answer. If you want a calendar that “just works” with no setup and no maintenance, Google Calendar is still the right answer, and the privacy cost might be acceptable to you. I am not going to tell you the privacy cost is worth it. I will tell you that the setup cost is real, and you should not underestimate it.
Bottom line
I have been running Baikal for three months across two adults, four devices, and about 40 events per month. The total downtime has been about 4 hours, all during a Docker upgrade I bungled. The sync works on cellular networks. The reminders fire. The shared family calendar updates in real time. The setup cost was higher than I expected, and the maintenance cost is lower than I expected. I am keeping it.
If you only do one thing from this article, set up a DuckDNS subdomain and a restic backup before you install Baikal. The two biggest gotchas I hit would have been avoided by 30 minutes of upfront infrastructure work.