Self-Hosting Cal.com: Open Source Scheduling Tool 2026
Cal.com is the open source Calendly alternative. Self-hosting gives you unlimited booking pages, unlimited event types, and team scheduling — all without per-user pricing.
Why Self-Host Cal.com
Calendly's pricing creates a frustrating ceiling for growing teams. The Standard plan at $10/user/month covers basic booking, but team scheduling requires the Teams plan at $16/user/month. For a 20-person team, that's $3,840/year just for scheduling links. Add required features like Salesforce integration or routing forms, and you're looking at custom enterprise pricing.
Self-hosting Cal.com on a €4.50/month Hetzner server gives you unlimited users, unlimited event types, team round-robin scheduling, and collective booking for approximately $54/year. No seat-based pricing, no feature gating.
Booking data privacy. Every time someone books a call with you through Calendly, their name, email, and meeting preferences go to Calendly's servers. For sales teams tracking prospect engagement, healthcare providers with HIPAA concerns, or anyone with client confidentiality obligations, self-hosting keeps that relationship data on your own infrastructure.
White-label branding. Self-hosted Cal.com can be deployed on your own domain with your own branding — cal.yourcompany.com rather than calendly.com/yourname. For customer-facing scheduling, this presents a more professional and consistent brand experience without paying for Calendly's premium branding removal.
Custom booking flows. Cal.com's open source nature means you can modify the booking confirmation flow, add custom questions, integrate with internal CRM systems via webhooks, or build completely custom booking experiences using Cal.com's embed API.
When NOT to self-host Cal.com: Cal.com is a complex Next.js application with a large dependency surface and multiple configuration requirements (OAuth credentials for each calendar provider). If you're an individual who just needs basic scheduling links, Calendly's free tier covers simple use cases without server management. Also, Cal.com's Google and Microsoft calendar OAuth setup requires creating Google Cloud Console and Azure applications — this is a non-trivial prerequisite that trips up some self-hosters.
Prerequisites
Cal.com has more setup complexity than simpler self-hosted tools because it integrates directly with external calendar services via OAuth. Plan time for the calendar integration setup steps. Choosing a reliable VPS provider for self-hosting matters since scheduling reliability directly affects your ability to take meetings.
Server specs: 2 GB RAM minimum is accurate for a small team (under 20 users). For a larger organization with high booking volume, 4 GB RAM provides comfortable headroom for the Next.js server-side rendering and database queries. The PostgreSQL database is the primary RAM consumer beyond the Node.js process.
Two secrets to generate before starting: NEXTAUTH_SECRET (used for session encryption) and CALENDSO_ENCRYPTION_KEY (used for calendar credential encryption). Both must be generated before first start and must never change after initial setup — changing them invalidates all stored calendar credentials and active sessions.
Google OAuth setup: To enable Google Calendar sync (the most requested integration), you need to create a Google Cloud Console project, enable the Calendar API and People API, configure the OAuth consent screen, and create OAuth 2.0 credentials with the correct redirect URI. Budget 30-45 minutes for this setup. The credentials go in GOOGLE_API_CREDENTIALS as a JSON string.
SMTP configuration: Booking confirmations, reminder emails, and cancellation notices all go through SMTP. Configure a transactional email service (Resend, Postmark, or Mailgun) before inviting users — their account verification emails depend on it.
Operating system: Ubuntu 22.04 LTS. Cal.com's Docker image is maintained by the Cal.com team and targets standard Ubuntu/Debian environments.
Skills required: Comfortable with multi-step environment variable configuration, OAuth application setup in external developer consoles (Google Cloud, Azure), and basic Docker Compose operations.
Requirements
- VPS with 2 GB RAM minimum
- Docker and Docker Compose
- Domain name (e.g.,
cal.yourdomain.com) - 10+ GB disk
- SMTP service for booking notifications
Step 1: Clone and Configure
git clone https://github.com/calcom/cal.com.git
cd cal.com
# Copy environment
cp .env.example .env
Step 2: Configure Environment
Edit .env:
# App
NEXT_PUBLIC_WEBAPP_URL=https://cal.yourdomain.com
NEXTAUTH_SECRET=your-random-secret-min-32-chars
CALENDSO_ENCRYPTION_KEY=your-random-encryption-key-32-chars
# Database
DATABASE_URL=postgresql://calcom:your-strong-password@db:5432/calcom
DATABASE_DIRECT_URL=postgresql://calcom:your-strong-password@db:5432/calcom
# Email
EMAIL_FROM=cal@yourdomain.com
EMAIL_SERVER_HOST=smtp.resend.com
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER=resend
EMAIL_SERVER_PASSWORD=re_your_api_key
# Calendar integrations (get from Google Cloud Console)
GOOGLE_API_CREDENTIALS={"client_id":"...","client_secret":"..."}
# Microsoft Calendar (get from Azure Portal)
# MS_GRAPH_CLIENT_ID=your-client-id
# MS_GRAPH_CLIENT_SECRET=your-client-secret
Generate secrets:
openssl rand -hex 32 # NEXTAUTH_SECRET
openssl rand -hex 16 # CALENDSO_ENCRYPTION_KEY (must be 32 chars)
Step 3: Docker Compose Setup
# docker-compose.yml
services:
calcom:
image: calcom/cal.com:latest
container_name: calcom
restart: unless-stopped
ports:
- "3000:3000"
env_file: .env
depends_on:
- db
db:
image: postgres:16-alpine
container_name: calcom-db
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=calcom
- POSTGRES_USER=calcom
- POSTGRES_PASSWORD=your-strong-password
volumes:
postgres_data:
Step 4: Start Cal.com
docker compose up -d
Step 5: Reverse Proxy (Caddy)
# /etc/caddy/Caddyfile
cal.yourdomain.com {
reverse_proxy localhost:3000
}
sudo systemctl restart caddy
Step 6: Initial Setup
- Open
https://cal.yourdomain.com - Create your admin account
- Set your timezone and availability
Step 7: Connect Calendars
Google Calendar:
- Go to Google Cloud Console
- Create a project and enable Calendar API
- Create OAuth 2.0 credentials
- Add
https://cal.yourdomain.com/api/integrations/googlecalendar/callbackas redirect URI - Add credentials to
.env
Microsoft Outlook:
- Go to Azure Portal
- Register an application
- Add Calendar.ReadWrite permissions
- Add
https://cal.yourdomain.com/api/integrations/office365calendar/callbackas redirect URI
Apple Calendar:
- Connect via CalDAV in Cal.com settings
Step 8: Create Event Types
| Event Type | Duration | Use Case |
|---|---|---|
| Quick Chat | 15 min | Initial calls, quick questions |
| Discovery Call | 30 min | Sales conversations |
| Team Meeting | 30 min | Internal sync |
| Deep Dive | 60 min | Technical discussions |
| Pair Programming | 90 min | Collaborative coding |
For each event type, configure:
- Duration and buffer time
- Availability windows (e.g., Mon-Fri 9am-5pm)
- Minimum notice (e.g., 4 hours)
- Questions for invitees
- Confirmation email template
- Meeting location (Zoom, Google Meet, phone)
Step 9: Team Scheduling (Optional)
Set up team features:
- Settings → Teams → create a team
- Invite team members
- Create team event types:
- Round Robin — distributes bookings evenly across team
- Collective — requires all team members to be available
- Managed Event Types — admin controls event type settings for all members
Step 10: Embed on Your Website
<!-- Inline embed -->
<iframe
src="https://cal.yourdomain.com/your-username/30min"
width="100%"
height="700"
frameborder="0"
></iframe>
<!-- Or use Cal.com embed snippet -->
<script>
(function (C, A, L) {
let p = function (a, ar) { a.q.push(ar); };
let d = C.document;
C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments;
if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A;
cal.loaded = true; }
if (ar[0] === L) { const api = function () { p(api, arguments); };
const namespace = ar[1]; api.q = api.q || [];
typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar);
return; }
p(cal, ar);
};
})(window, "https://cal.yourdomain.com/embed/embed.js", "init");
Cal("init");
Cal("ui", {"styles":{"branding":{"brandColor":"#000000"}}});
</script>
Production Hardening
Backups:
# Database backup (daily cron)
docker exec calcom-db pg_dump -U calcom calcom > /backups/calcom-$(date +%Y%m%d).sql
Set up offsite backups with automated server backups using restic. The PostgreSQL database contains all your users' booking preferences, calendar credentials, and event type configurations — losing this data means your users need to reconnect their calendars from scratch.
Updates:
docker compose pull
docker compose up -d
Monitoring:
- Monitor port 3000 with Uptime Kuma
- Set up alerts for failed email delivery
- Monitor PostgreSQL disk usage
Resource Usage
| Users | RAM | CPU | Disk |
|---|---|---|---|
| 1-10 | 2 GB | 2 cores | 10 GB |
| 10-50 | 4 GB | 2 cores | 20 GB |
| 50+ | 8 GB | 4 cores | 30 GB |
VPS Recommendations
| Provider | Spec | Price |
|---|---|---|
| Hetzner | 2 vCPU, 4 GB RAM | €4.50/month |
| DigitalOcean | 2 vCPU, 2 GB RAM | $12/month |
| Linode | 1 vCPU, 2 GB RAM | $12/month |
Production Security Hardening
Cal.com stores OAuth tokens for your users' Google and Microsoft calendars — this is sensitive credential data that grants read/write access to people's schedules. The self-hosting security checklist applies in full; here are Cal.com-specific priorities.
UFW firewall:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 3000/tcp # Block direct Cal.com access
sudo ufw enable
Fail2ban for SSH:
sudo apt install fail2ban -y
/etc/fail2ban/jail.local:
[sshd]
enabled = true
maxretry = 5
bantime = 3600
findtime = 600
Protect the .env file: The .env contains NEXTAUTH_SECRET, CALENDSO_ENCRYPTION_KEY, and OAuth credentials. These are the most sensitive values in the deployment.
chmod 600 .env
echo ".env" >> .gitignore
Never change the encryption key after first run. The CALENDSO_ENCRYPTION_KEY encrypts stored calendar OAuth tokens. If you rotate this key, every user's calendar connection becomes invalid and must be reconnected manually.
Disable SSH password auth:
PasswordAuthentication no
PermitRootLogin no
Restart: sudo systemctl restart sshd
Automatic security updates:
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
OAuth redirect URI security: In your Google Cloud Console and Azure Portal, only register the exact redirect URIs your Cal.com installation uses. Do not use wildcard URIs — this prevents an attacker from redirecting OAuth flows to their own server if they find an open redirect in your application.
Troubleshooting Common Issues
Google Calendar not showing availability
The most common cause is incomplete OAuth scope configuration. Your Google Cloud Console OAuth application needs both calendar.readonly (to check availability) and calendar.events (to create booking events). Verify the scopes on your OAuth consent screen. Also check that the redirect URI https://cal.yourdomain.com/api/integrations/googlecalendar/callback is exactly listed in your Google Cloud credentials — extra slashes or wrong protocol cause OAuth failures.
Booking confirmations not sending
Cal.com sends emails through the EMAIL_SERVER_* variables via NextAuth's email provider. Test SMTP connectivity: docker exec calcom node -e "require('nodemailer').createTransport({host:'smtp.resend.com',port:587,auth:{user:'resend',pass:'YOUR_KEY'}}).verify(console.log)". If verification fails, check that port 587 isn't blocked by your VPS provider and that your API key is correct.
"Invalid encryption key" errors after update
If you see credential decryption errors after pulling a new Cal.com image, a database migration may have changed the encryption format. Check docker logs calcom for the specific error. In most cases, the CALENDSO_ENCRYPTION_KEY in your .env has changed accidentally. Restore the original key from your backup — never generate a new one to "fix" this error, as that permanently corrupts all stored credentials.
Booking page shows wrong timezone
Cal.com respects the TZ environment variable in the container and the user's browser timezone for display. If availability slots appear wrong, verify the NEXT_PUBLIC_WEBAPP_URL is correct (used for time calculations) and that your server's system timezone is set correctly: timedatectl set-timezone America/New_York.
Round-robin scheduling not distributing evenly
Round-robin requires all team members to have connected calendars and set their availability windows. If one team member's calendar is disconnected, Cal.com may always assign to others. Check each team member's calendar connection status in their profile settings. Also verify that all team members have overlapping availability windows for the event type's duration.
Ongoing Maintenance and Operations
Cal.com is a scheduling-critical application — booking links in your email signature, on your website, and in marketing materials all depend on it being available. Here's what long-term operation looks like.
Calendar sync reliability. The most common support request for self-hosted Cal.com is "my availability shows wrong." This happens when a user's Google or Microsoft OAuth token expires and calendar sync silently stops. OAuth tokens expire after a period of inactivity or when the user's password changes. Build a process to periodically verify calendar connections: in your Cal.com admin, check Settings → Connected Calendars monthly and notify users whose connections appear disconnected.
Booking link updates. When team members change their availability, leave the company, or join a new team, booking links need to be updated. Establish a process: new hires configure their Cal.com profile on day one, departing team members' profiles are deactivated before their last day. Orphaned booking links that still appear active but lead to 404 pages or "no availability" screens create a poor experience for prospective customers.
Event type maintenance. As your product and team evolve, event types need updating. Old event types (e.g., a discontinued product demo) should be archived rather than deleted — deletion breaks existing booking links. Cal.com supports disabling event types so they're hidden from your booking page but existing links redirect gracefully.
Webhook integrations for CRM sync. Cal.com fires webhooks on booking created, cancelled, and rescheduled events. Connect these to your CRM (via Zapier, n8n, or a custom webhook consumer) to automatically create contact records and activities when someone books a call. This is one of the highest-value integrations for sales teams — booked demos automatically populate the CRM without manual data entry.
Database backup before updates. Cal.com's database contains user profiles, event types, booking history, and calendar credentials. The encrypted calendar credentials are particularly important — losing them means every user must reconnect their calendars. Back up before every update and verify restores work.
Team scheduling optimization. For teams using round-robin scheduling, periodically review distribution metrics to ensure bookings are distributed as expected. If one team member consistently receives disproportionately fewer bookings, check their availability windows — they may have inadvertently restricted their availability or have calendar conflicts blocking slots.
Embedding updates. If you update your Cal.com URL or event types, update all embedded booking widgets on your website. Embedded iframes using hardcoded event type URLs continue pointing to old configuration until updated. Audit your website for embedded Cal.com widgets quarterly and verify they still point to active event types.
Integrating bookings with your CRM and sales stack. Cal.com fires webhook events for every booking action — created, rescheduled, cancelled, and completed. Connect these events to your CRM via a simple webhook consumer or n8n workflow to automatically create contact records when someone books a call, log call activities against existing contacts, and trigger automated follow-up sequences after meetings end. This integration transforms Cal.com from a standalone scheduling tool into a full-funnel sales process component. Configure the webhook in Settings → Developer → Webhooks, and map the payload fields (name, email, event type, start time) to your CRM's API. The self-hosted nature means you control exactly what data is forwarded and can enrich it with internal context before sending.
Compare scheduling tools on OSSAlt — features, integrations, and pricing side by side.