Why I Stopped Using Google Calendar for My Team (The Specific Moment)
The moment that broke my Google Calendar dependency wasn’t philosophical — it was a Friday afternoon email from a client’s IT security team saying their policy prohibited any data processed by Google Workspace. We had a team of eight people coordinating across two time zones, and I had 48 hours to stand up a replacement before Monday’s sprint planning. That specific panic is what I wish someone had written a guide for.
Here’s the problem I ran into immediately: every “self-hosted Google Calendar alternative” article I found was conflating three completely different things. A CalDAV server (like Baikal or Radicale) is just a protocol endpoint — it stores calendar data and syncs to your native apps, but has zero web UI. A WebUI frontend (like InfCloud bolted onto Baikal) gives you a browser interface but relies on that CalDAV backend. A full scheduling app (like Nextcloud Calendar or Cal.com) bundles everything but brings 200MB of dependencies along for the ride. Picking the wrong category means you either spend a weekend wiring pieces together that were never designed to talk to each other, or you deploy a 4GB Docker stack when all you needed was a 15MB binary.
What I actually needed — and what most teams in that situation need — was something that: speaks CalDAV natively so iOS, Android, and Thunderbird sync without plugins; has a usable browser UI for people who won’t touch a native app; and runs without a $40/month VPS. This guide covers tools I’ve personally run on a $5 Hetzner CX11 (2GB RAM, 20GB disk) and on a Raspberry Pi 4. I’ll show actual install commands, real config files, and the specific things that bit me that aren’t in the README.
One clarification on scope: I’m covering self-hosted web calendar tools specifically, not the broader ecosystem of task managers, note apps, or automation pipelines. If you’re building a broader self-hosted productivity stack, check out the Ultimate Productivity Guide: Automate Your Workflow in 2026 for how calendars fit into a full workflow automation setup. For this article, the criterion for making the list was simple — I had to be able to get it running from zero to functional in under two hours, on hardware a normal person would actually own.
The other thing I’ll be honest about upfront: none of these tools is perfect. Some have gorgeous UIs with CalDAV implementations that drop recurring events. Some have rock-solid sync with interfaces that look like 2009. I’ll tell you exactly which trade-off you’re signing up for before you spend three hours on an install.
Quick Comparison: What You’re Actually Choosing Between
The thing that trips people up most is conflating “calendar server” with “calendar app.” I wasted half a day setting up Radicale before realizing my non-technical users had no web interface to log into — they’d need a CalDAV-aware client already installed. So before anything else: Radicale and Baïkal are protocols servers, not apps. They speak CalDAV and serve .ics data. Without Thunderbird, iOS Calendar, or a separate frontend like InfCloud pointed at them, your users see nothing.
| Tool | Type | Min RAM | Native Web UI | Multi-User | Mobile Clients | Commit Activity |
|---|---|---|---|---|---|---|
| Nextcloud Calendar | Full platform + CalDAV | 512MB (realistically 1GB+) | Yes | Yes | iOS, Android (DAVx⁵) | Active (weekly) |
| Radicale | CalDAV/CardDAV server | ~64MB | No | Yes (config-based) | iOS, Android (DAVx⁵) | Moderate (monthly) |
| Baïkal | CalDAV/CardDAV server | ~128MB | Admin panel only | Yes | iOS, Android (DAVx⁵) | Slow (sporadic) |
| Cal.com | Scheduling app (booking) | 1GB+ (Node + Postgres) | Yes | Yes | Browser-based | Very active (daily) |
| InfCloud | CalDAV web frontend | ~32MB (static files) | Yes (is the UI) | Depends on backend | Via backend | Stale (2021) |
The pairing pattern that actually works: Radicale or Baïkal as the CalDAV backend, InfCloud dropped in front as a static web app that authenticates directly against it. Your users get a browser UI, your server stays under 200MB total. The catch is that InfCloud’s last meaningful commit was in 2021, so you’re betting on stable software, not maintained software. For most calendar use cases that’s fine — CalDAV isn’t changing — but don’t expect bug fixes.
Docker Is Not the Only Option Anymore: What I Actually Run Containers With in 2026
Cal.com is a completely different animal and gets miscategorized constantly. It doesn’t store your calendar data the way Nextcloud does. It’s a scheduling layer — think Calendly but self-hosted. You connect it to an existing Google Calendar or CalDAV source, and it handles availability, booking pages, and meeting links. If someone asks “can I book time with you?” that’s Cal.com’s job. If someone asks “where do I see my team’s shared calendar?” that’s Nextcloud or Radicale’s job.
One-sentence positioning for each, so you can skip the sections that don’t apply to you:
- Nextcloud Calendar — the right pick if you want a full Google Workspace replacement and don’t mind running PHP + a real database.
- Radicale — best for a personal or small-team CalDAV server where everyone already has a native calendar app on their phone or desktop.
- Baïkal — Radicale with a slightly friendlier admin panel and SQLite/MySQL storage, but the project moves slowly.
- Cal.com — the only choice here if your goal is external scheduling and appointment booking, not internal calendar management.
- InfCloud — not a standalone product; only relevant as the web UI you bolt onto Radicale or Baïkal when your users refuse to install anything.
1. Nextcloud Calendar — The One That Does Too Much (In a Good Way If You’re Already There)
The first thing I tell anyone considering Nextcloud Calendar is this: if you don’t already have a Nextcloud instance, close this tab and pick something else from this list. Nextcloud is a 2GB+ RAM minimum infrastructure decision disguised as a calendar app. But if you’re already running it? Installing the calendar app takes about 15 seconds.
# SSH into your server, then:
sudo -u www-data php occ app:install calendar
# Verify it installed and check the version
sudo -u www-data php occ app:list | grep calendar
That’s genuinely it. No separate database setup, no port juggling, no systemd unit to write. It plugs directly into your existing Nextcloud users, groups, and storage. For teams already authenticating against Nextcloud, this means one less login, shared calendar visibility is handled by the same sharing model they already use for files, and you don’t have to explain another tool.
The gotcha that burned me: iOS CalDAV sync worked immediately — zero config. Android with DAVx⁵ silently failed for three days before I figured out the problem. DAVx⁵ hits /.well-known/caldav during autodiscovery, and if your nginx vhost doesn’t redirect that to Nextcloud’s actual endpoint, DAVx⁵ just… gives up without a clear error. Here’s the nginx snippet that fixed it:
server {
# ... your existing Nextcloud server block ...
# Without these, DAVx5 on Android silently fails autodiscovery
location = /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
}
After adding those two location blocks and reloading nginx, DAVx⁵ synced immediately. The Nextcloud docs do mention this, but it’s buried three pages deep in the admin section and the error you get on Android gives you no indication this is the cause. Apache users need equivalent Redirect directives — same logic, different syntax.
Honest resource picture: on a 1GB RAM VPS, Nextcloud with the calendar app will page under real load. PHP-FPM, the Nextcloud cron job, and any background sync tasks compete hard for that memory. I’d call 2GB RAM the real minimum for a usable experience, not 1GB. The other pain point is upgrades — Nextcloud core upgrades occasionally bump the minimum required calendar app version, but if the new calendar version has a bug, you’re stuck waiting for a patch release before upgrading core, or you upgrade core and break calendar. This has happened to me twice in the last year.
- Best for: Teams of 5–50 already on Nextcloud who want integrated file sharing + calendar without an additional auth layer
- Skip if: You only need a calendar — the operational overhead of Nextcloud for that single use case is genuinely not worth it
- Real cost: $0 software, but plan for at least a €6–8/month VPS (2GB RAM, 2 vCPU) to run it without constant performance complaints
2. Radicale — ‘I Just Need CalDAV and I Want Zero Overhead’
The thing that surprised me most about Radicale is how little it does — and how that’s exactly the point. Most calendar servers try to be a platform. Radicale is a process. It listens on a port, speaks CalDAV, stores .ics files in a directory. That’s it. I switched to it after spending two hours trying to configure Nextcloud for a three-person dev team who just needed shared calendar sync. Radicale took 15 minutes.
The install sequence is genuinely this short:
# Python 3.8+ required; 3.11 works fine
pip install radicale passlib bcrypt
# Create the user (bcrypt is more secure than md5 here — passlib handles it)
htpasswd -B -c /etc/radicale/users yourname
# Create storage dir with correct ownership
mkdir -p /var/lib/radicale/collections
chown -R radicale:radicale /var/lib/radicale
Then drop this into /etc/radicale/config:
[server]
hosts = 127.0.0.1:5232
# SSL is handled by nginx upstream — don't double-encrypt
[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = bcrypt
[storage]
filesystem_folder = /var/lib/radicale/collections
[logging]
level = warning
Start it with python -m radicale --config /etc/radicale/config and throw a systemd unit behind it. Your nginx block just needs proxy_pass http://127.0.0.1:5232; with your normal SSL termination. The whole thing uses maybe 20MB of RAM under normal load. For Docker, the official pattern is even simpler — mount /var/lib/radicale as a volume and you’re done in under 10 minutes.
Here’s what nobody puts in the getting-started guide: there is no web UI. No login screen, no calendar view, no admin panel. When a non-technical user asks “how do I see my calendar?” the answer is “install DAVx⁵ on Android, use the built-in iOS Calendar app, or add an account in Thunderbird.” If anyone on your team will be confused by that workflow, stop reading and go look at Baïkal or Nextcloud instead. Radicale has zero tolerance for UI expectations.
The version 3.x migration gotcha bit me personally. If you’re running Radicale 2.x — which a lot of older tutorials still reference — the on-disk storage format changed in version 3. The collection structure was reorganized and migrating is not automatic. There’s a migration script in the repo, but it requires manual intervention and you should back up /var/lib/radicale before touching anything. If you’re doing a fresh install today, always install from PyPI (current stable is 3.x) and you’ll never hit this.
Radicale is the right answer when your requirements are: CalDAV sync for a developer or small technical team, minimal attack surface, no database dependency, and you want something you can fully understand by reading one config file. It’s the wrong answer the moment someone needs to share calendars through a browser or invite non-technical users.
3. Baïkal — The Middle Ground Between Radicale and a Full App
Baïkal — The Middle Ground Between Radicale and a Full App
Most people hit Baïkal because Radicale felt too barebones (no admin UI, config via text files) but Nextcloud felt like swatting a fly with a sledgehammer. Baïkal gives you a real web-based admin panel where you can create users, assign calendars, and set permissions — all without SSH. That said, don’t confuse “admin UI” with “calendar UI.” Your users still connect via CalDAV clients like Thunderbird, Apple Calendar, or DAVx⁵. Nobody logs into a browser to see their events. That distinction trips people up constantly.
The cleanest way to run it is with the ckulka/baikal image — the official-ish community image that’s kept reasonably up to date. Here’s a Compose file that actually works without fighting volume permissions:
version: "3.8"
services:
baikal:
image: ckulka/baikal:nginx
container_name: baikal
restart: unless-stopped
volumes:
- ./baikal/config:/var/www/baikal/config # persists your config.php
- ./baikal/data:/var/www/baikal/Specific # where flat-file calendar data lives
ports:
- "127.0.0.1:8800:80" # bind to localhost only, nginx handles TLS
nginx:
image: nginx:1.25-alpine
container_name: baikal-proxy
restart: unless-stopped
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx/baikal.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
Your nginx config for this should forward /.well-known/caldav and /.well-known/carddav with proper redirects — otherwise iOS and macOS auto-discovery fails silently and you’ll spend an hour wondering why Apple Calendar “can’t find” the server:
server {
listen 443 ssl;
server_name cal.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/cal.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cal.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8800;
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;
}
# Apple auto-discovery — without this, iOS setup is manual and annoying
location = /.well-known/caldav { return 301 /dav.php; }
location = /.well-known/carddav { return 301 /dav.php; }
}
The inode thing is a real gotcha that the docs don’t mention anywhere. Baïkal stores each calendar event as a separate .ics file on disk. A single user with 3 years of Google Calendar history can easily import 4,000–8,000 files. If you’re on a cheap VPS or shared host with inode limits (OVH’s starter plans, for example, often cap at 100K inodes for the entire partition), a team of 10 importing their full history will put you in a world of hurt fast. Check your inode count with df -i before you commit to this approach.
On the SQLite vs MySQL decision: make this call at setup time, not six months in. The Baïkal setup wizard asks you on first run. SQLite works fine for a family or a small team under roughly 15–20 active users — I’ve run it that way with no issues. Beyond that, query times for free/busy lookups start to drag, especially with lots of recurring events. Migrating from SQLite to MySQL after the fact means exporting all data, wiping the DB config, and re-importing — there’s no built-in migration tool. If you think you’ll scale, pick MySQL now. The setup takes an extra five minutes; the migration takes an afternoon.
If your users want to actually view calendars in a browser, Baïkal alone won’t cut it. The admin UI shows you users and calendar objects, not events. The standard pairing is InfCloud — a static HTML/JS CalDAV client you drop on a web server and point at your Baïkal endpoint. It’s not pretty, but it works and requires zero server-side code. That combo gives you roughly 80% of what people want from a hosted calendar without touching Nextcloud. Baïkal is the right call for a small business or family setup where one person manages the config and everyone else just needs their CalDAV client to sync reliably.
4. InfCloud — Pairing a Web UI with Your CalDAV Server
The thing that catches most people off guard with InfCloud is that it has zero server-side code. It’s a folder of static HTML and JavaScript you drop somewhere nginx can serve it. That’s genuinely useful — no PHP, no Node process, no database. But it means all the “magic” happens in the browser, talking directly to your CalDAV server via XMLHttpRequest. Which is exactly why CORS will ruin your afternoon if you’re not ready for it.
I run this exact stack for one client right now: Baïkal handling CalDAV on a $6/month Hetzner VPS (CX11, 2GB RAM, more than enough), InfCloud served as static files by the same nginx instance. Baïkal lives at cal.example.com, InfCloud at calendar.example.com. Both behind nginx with Let’s Encrypt. Total monthly cost hasn’t changed in two years.
The Three Lines in config.js That Actually Matter
InfCloud ships a config.js file and most of it you can leave alone. But these three will break everything if wrong:
// config.js — only showing the lines you'll touch first
// 1. Point this at your CalDAV server's principal URL, not just the domain
GlobalNetworkCheckSettings = {
href: 'https://cal.example.com/dav.php/',
hrefLabel: null,
crossDomain: true, // required if InfCloud is on a different origin
forceReadOnly: null,
settingsType: 'GlobalNetworkCheckSettings',
checkContentType: true
};
// 2. Must be 'GlobalNetworkCheckSettings' if you're using the above
GlobalSettingsType = 'GlobalNetworkCheckSettings';
// 3. Pick your poison — 'yyyy/mm/dd' or 'mm/dd/yyyy' or 'dd/mm/yyyy'
GlobalDateTimeFormat = 'dd/mm/yyyy';
Get GlobalSettingsType wrong and you get a blank white screen with zero console errors. InfCloud just silently does nothing. This burned me for 40 minutes the first time. Get the href trailing slash wrong on Baïkal and you’ll get a 301 redirect that the browser’s XHR won’t follow correctly. Always check the exact URL Baïkal exposes by hitting it in a regular browser tab first — Baïkal’s principal URL ends in /dav.php/.
The CORS Problem (With the nginx Fix)
If InfCloud and Baïkal are on different subdomains, your browser will block every CalDAV request with a CORS error. Here’s the nginx block for your Baïkal vhost that actually fixes it — the OPTIONS handling is mandatory because CalDAV clients send preflight requests:
server {
listen 443 ssl;
server_name cal.example.com;
# ... your SSL config here ...
location / {
# Replace with the exact origin InfCloud is served from
add_header 'Access-Control-Allow-Origin' 'https://calendar.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PROPFIND, REPORT, MKCALENDAR' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Depth, Prefer' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# Handle preflight without passing to PHP
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://calendar.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PROPFIND, REPORT, MKCALENDAR';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Depth, Prefer';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Length' 0;
return 204;
}
# ... rest of your PHP/FastCGI config ...
}
}
The mistake people make is adding CORS headers only to the location ~ \.php$ block. CalDAV uses WebDAV methods like PROPFIND that nginx routes differently — put the headers on the parent location / block or you’ll fix GET requests and break everything else. Also: Depth must be in Allow-Headers or calendar listing fails silently.
Honest Assessment of the UI
It works. Events show up, you can create and edit them, recurring events behave correctly, and it handles multiple calendars per user. But the visual design is genuinely 2012 vintage — fixed-width layout, gray gradients, small click targets. I told my client upfront not to compare it to Google Calendar and they’ve been fine with it. Their use case is internal scheduling for a 6-person team, not impressing anyone. If you need to demo this to a stakeholder who expects Notion-level polish, do not use InfCloud. Use Nextcloud Calendar or just host a Framadate instance for lighter needs.
Where this combination shines is the operational simplicity. The entire stack — Baïkal + InfCloud + nginx — uses maybe 80MB of RAM at idle. No Docker, no background workers, no queues. Baïkal updates are manual (it’s composer-based), InfCloud updates are literally downloading a new zip. For small teams that need browser-accessible shared calendars without spinning up a full Nextcloud instance just for the calendar app, this is the right call. Native CalDAV sync still works for everyone on iOS or Thunderbird — InfCloud is just the fallback for people who refuse to configure a calendar client.
5. Cal.com — When You Actually Need Scheduling, Not Just a Calendar
Most people searching “self-host a web calendar” land on Cal.com and assume it does what Nextcloud Calendar or Radicale does. It absolutely does not. Cal.com is a scheduling tool — think Calendly, not Google Calendar. I’m covering it here specifically because the confusion is rampant, and if it’s actually what you need, nothing else on this list will solve your problem.
The use case split is clean: if your problem is “I need my phone, laptop, and browser to show the same events,” you want a CalDAV server. If your problem is “I need clients, candidates, or teammates to book a 30-minute slot with me without 14 emails,” Cal.com is the answer. That second pain point is genuinely underserved by self-hosted software, and Cal.com solves it well enough that teams pay Calendly $16/user/month to avoid it.
The Setup Is Heavier Than You Expect
This is not a Saturday afternoon project. The stack requires Node.js 18+, PostgreSQL (14+ works, 15+ recommended), and a .env file that ships with roughly 40 variables you need to populate. The two that will wreck your first boot if you skip them:
# These two will produce cryptic errors if missing or wrong
DATABASE_URL="postgresql://calcom:yourpassword@localhost:5432/calcom"
NEXTAUTH_SECRET="generate-this-with-openssl-rand-base64-32"
If NEXTAUTH_SECRET is missing, you get a redirect loop on login with zero explanation in the UI. The database URL being malformed gives you a Prisma connection error in the logs but a generic 500 in the browser. Run their Docker Compose setup from the repo to save yourself the dependency headache — it bundles Postgres and handles the migration step automatically:
# Clone their repo and use the compose file they maintain
git clone https://github.com/calcom/cal.com.git
cd cal.com
cp .env.example .env
# Edit .env — seriously, all 40 vars
docker compose up -d
# Watch for the prisma migrate step completing before hitting the UI
docker compose logs -f cal
It Reads Your Calendar — It Doesn’t Replace It
This is the distinction the docs bury: Cal.com connects to your existing Google Calendar or Outlook to check your availability. It doesn’t store your events. It doesn’t sync across devices. When someone tries to book Tuesday at 2pm, Cal.com calls the Google Calendar API, sees you have a dentist appointment, and blocks that slot. Your events live in Google/Outlook; Cal.com just reads them. If you disconnect Google, Cal.com goes blind to your real schedule and will happily double-book you.
Setting up that OAuth connection (Google especially) requires creating credentials in Google Cloud Console, and their docs assume you already know how to do that. You’ll need a verified OAuth app for production use, which means going through Google’s verification process if you’re booking with people outside your own Google Workspace. Most people don’t hit this until they’re two hours into setup and already annoyed.
Honest Cons of the Self-Hosted Version
- Feature lag is real. The cloud version ships things weeks or months before they appear in the open-source repo. If you see a feature on their marketing site, check the GitHub issues before assuming it’s in the self-hosted build.
- Stripe integration requires manual config. The docs mention it in one paragraph. In practice you need to set four Stripe env vars, configure webhook endpoints, and handle the test/live mode switch separately. Budget an extra hour.
- Video conferencing (Zoom, Google Meet) needs its own OAuth apps. Each integration is its own credentials setup. The docs link to external guides and hope for the best.
- No built-in email server. You need to supply SMTP credentials or an API key for Sendgrid/Postmark, or booking confirmations simply don’t send. Easy to miss in the initial setup checklist.
Cal.com self-hosted makes sense for consultants billing hourly who need booking pages and refuse to pay Calendly’s per-seat pricing, agencies managing booking flows for multiple clients under one instance, or anyone with genuine privacy requirements around client scheduling data. If you’re a solo developer who just wants to see your own calendar in a browser tab, this is about 10x more infrastructure than you need — go look at Nextcloud or a simple CalDAV setup instead.
The One I Ripped Out: Why I Don’t Recommend Agenda/OwnCloud Calendar Anymore
The thing that cost me the most time wasn’t a bad setup — it was a setup that worked fine for two years and then quietly became a liability. I ran ownCloud 10.x with its bundled calendar app for a small team, and the rot was gradual enough that I almost missed it. PHP dependency conflicts started appearing. Security advisories came in slower. Pull requests on the calendar app repo sat open for months. By the time I audited it seriously, the writing had been on the wall for a long time.
The ownCloud 10.x codebase is aging in a way that matters practically, not just philosophically. The community split happened years ago when Nextcloud was forked, and the momentum went with Nextcloud almost immediately. If you look at GitHub commit velocity, extension availability, and forum activity right now, ownCloud Calendar isn’t a competitor — it’s a legacy choice. I’m not saying it’s broken, I’m saying that starting a new self-hosted calendar deployment on it in 2023 is choosing a path with a shorter runway than you probably want. The team who might fix your bug in six months has largely moved over to Nextcloud.
The fork war is effectively over. Nextcloud won. I don’t say that to be dramatic — I say it because if you’re doing an honest evaluation right now, you need to know that the answer to “which one has better long-term support, more active plugins, and a bigger pool of people who can help me debug this CalDAV sync issue at 11pm” is unambiguously Nextcloud. ownCloud still exists, ownCloud Infinite Scale (oCIS) is a real rewrite effort, but the calendar-specific story there is thin and the migration path is its own project. Don’t let a decade-old blog post comparing them send you down the wrong fork.
One category I’m intentionally leaving out: Kerio Connect, Zimbra, and similar enterprise CalDAV servers. Those are paid products with per-mailbox licensing, dedicated support contracts, and a completely different operational profile. They’re not self-hosting in the DIY sense — you’re buying a managed appliance you happen to run on your own hardware. That’s a legitimate choice for a 200-person company with an IT department, but it’s out of scope here. I’m focused on open-source options you can stand up yourself without a vendor relationship.
If you inherited an ownCloud deployment, the practical move is to plan a Nextcloud migration rather than continuing to invest in the existing stack. The migration isn’t trivial — data structures differ enough that you’ll want to test it on a staging instance first — but Nextcloud publishes a migration guide and the community has documented the edge cases well. Staying on ownCloud Calendar because migration is annoying is a short-term optimization that tends to become a medium-term incident.
When to Pick What: The Decision Tree I Actually Use
Most “which tool should I use” guides give you a feature comparison table and leave you to figure it out. Here’s the actual branching logic I use when someone asks me to recommend a self-hosted calendar setup.
Do you need external booking pages?
If anyone outside your organization needs to book time with you — clients, interview candidates, anyone clicking a link to pick a slot — the answer is Cal.com and nothing else on this list comes close. None of the other four options have that workflow built in. You’d be bolting on a separate tool anyway, so just start with Cal.com. Fair warning: it needs 1GB+ RAM comfortably, and the self-hosted version requires a Postgres 13+ database, Redis, and a Node 18+ runtime. The docker-compose setup works, but the first deploy takes longer than the docs imply. If you don’t need booking pages, keep reading.
Are your users non-technical and need a browser UI?
This is the fork most people get wrong. If someone on your team is going to open a browser tab, click around, and expect a Google Calendar-like experience, you have two real options: Nextcloud or Baïkal with InfCloud bolted on. Nextcloud’s calendar UI is genuinely polished — drag to reschedule, color-coded calendars, sharing dialogs. InfCloud is functional but looks like 2014. The catch with Nextcloud is RAM, which I’ll quantify below. If every user is comfortable syncing via a native client (Apple Calendar, Thunderbird, GNOME Calendar), skip the browser UI entirely and drop down to Radicale.
Are you already running Nextcloud for files or notes?
If yes, enabling the Calendar app takes about 30 seconds:
# From your Nextcloud server directory
sudo -u www-data php occ app:enable calendar
# Verify it's active
sudo -u www-data php occ app:list | grep calendar
You already paid the RAM cost for Nextcloud’s PHP-FPM workers and the database. Calendar is essentially free on top of that. But if you’re only considering Nextcloud because you want a calendar, stop. You’d be running a 400MB Docker image (minimum), configuring cron jobs for background tasks, managing an Nginx + PHP-FPM + MariaDB stack, and updating a major version every six months — all to get a CalDAV server you could’ve had with a 5MB Python process.
Is this just for you or a couple of technical people?
Then the answer is Radicale. The entire server config looks like this:
[server]
hosts = 0.0.0.0:5232
[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = bcrypt
[storage]
filesystem_folder = /var/lib/radicale/collections
# Add a user
htpasswd -B /etc/radicale/users alice
# Run it (or drop this in a systemd unit)
radicale --config /etc/radicale/config
Twenty minutes from zero to syncing with Apple Calendar or Thunderbird. No database. Collections are just files on disk you can rsync for backups. I’ve run this for years on a $4/mo VPS without touching it. The only gotcha: Radicale’s built-in rights management is coarse, so if you need per-calendar ACLs across 10+ users, you’ll outgrow it fast.
The RAM reality check — actual numbers matter here
- Radicale: under 50MB RSS in practice. Handles the occasional CalDAV sync and sits idle otherwise.
- Baïkal: ~80MB — it’s PHP, so you’re paying for PHP-FPM workers whether you’re using it or not. Pair it with InfCloud and add maybe 5MB more (it’s pure JS).
- Nextcloud (calendar only, minimum): 512MB RAM, and you’ll hit swap during background jobs on a 1GB VPS. Budget 1GB just for Nextcloud; 2GB if you’re also doing file syncing.
- Cal.com: 1GB+ recommended. Next.js + tRPC + Prisma + Redis all running simultaneously. On a 512MB server it’ll start and immediately OOM under any real load.
The decision tree collapses fast once you know your RAM ceiling. A $6/mo VPS with 1GB RAM? Radicale or Baïkal. A dedicated box with 4GB+ already running other services? Nextcloud becomes viable. Anything in between where you’re tempted to squeeze Cal.com — don’t. The OOM kills will make you miserable.
The Setup I’d Recommend for a 10-Person Team Starting from Scratch Today
After testing every option on this list, I keep coming back to the same stack for small teams: Baïkal for the CalDAV/CardDAV backend, InfCloud as the browser UI, and Caddy handling TLS termination. The whole thing runs comfortably on a $6/month VPS (Hetzner CX11 or DigitalOcean’s basic droplet). Caddy’s automatic HTTPS via Let’s Encrypt alone is worth it — I’ve watched people spend two hours wrestling with certbot and nginx configs that Caddy handles in four lines.
Here’s the Caddy config that reverse proxies both services. Baïkal handles the actual CalDAV protocol; InfCloud is just static files that talk to Baïkal’s API directly from the browser:
# /etc/caddy/Caddyfile
yourdomain.com {
# InfCloud — served from its own directory
root * /var/www/infcloud
file_server
# Baïkal CalDAV/CardDAV backend
handle /baikal/* {
reverse_proxy localhost:8080
}
# Redirect bare CalDAV root so clients don't 404 on first sync
redir /.well-known/caldav /baikal/dav.php/ 301
redir /.well-known/carddav /baikal/dav.php/ 301
}
The URL format is the single thing that breaks every first-time setup. On Android with DAVx⁵, the server URL must be https://yourdomain.com/baikal/dav.php/ — with the trailing slash. Without it, DAVx⁵ either fails silently or shows a cryptic authentication error that has nothing to do with auth. Select “Login with URL and username” rather than email-based login. Once you’re in, DAVx⁵ will discover all calendars and address books automatically — don’t skip the discovery step by hardcoding a calendar path.
iOS is actually easier, but the menu path trips people up. Go to Settings → Calendar → Accounts → Add Account → Other → Add CalDAV Account. Fill it in exactly like this:
- Server:
yourdomain.com— no protocol prefix, no path, just the bare domain - User name: the Baïkal username you created in the admin panel (not an email unless that’s literally the username)
- Password: Baïkal account password
- Description: anything — this is just a label in your accounts list
iOS auto-discovers the /.well-known/caldav redirect, which is why the Caddy config above includes those two redir lines. If you skip them, iOS setup works but only after you add the full /baikal/dav.php/ path manually to the server field — and good luck explaining that to nine non-technical teammates.
Backups are where people get lazy and regret it later. Baïkal stores everything as flat files under /var/www/baikal/Specific/ (or wherever you installed it), which makes this cron job sufficient for a 10-person team:
# runs nightly at 2am, keeps 30 days of backups
0 2 * * * tar -czf /backups/baikal-$(date +\%Y\%m\%d).tar.gz /var/www/baikal/Specific/ && find /backups -name "baikal-*.tar.gz" -mtime +30 -delete
The Specific/ directory contains your SQLite database and all the vCal/vCard files. The rest of the Baïkal install is just application code you can re-download. I also rsync the /backups folder to a separate object storage bucket weekly — Backblaze B2 costs effectively nothing at this scale. The thing that catches people off guard: if you chose MySQL instead of SQLite during Baïkal setup, this tar approach misses your database entirely. Stick with SQLite for a team this size; there’s no performance reason to use MySQL until you’re well past 50 users.