Cal.com на сервере: частые ошибки и решения
Cal.com — открытая альтернатива Calendly, которую можно развернуть на своём сервере и полностью контролировать данные о встречах. На бумаге всё просто: docker compose up — и планировщик работает. На практике self-hosted Cal.com спотыкается о десяток мест, которых нет в Calendly: переменные окружения, миграции Prisma, вебхуки для видеозвонков, почта, которая либо не уходит, либо уходит в спам. Здесь — конкретные ошибки, с которыми реально сталкиваешься при развёртывании, и рабочие решения для каждой.
Содержание
- Как обычно разворачивают Cal.com и где здесь ловушки
- Ошибка: приложение падает при старте с ошибкой NEXTAUTH_SECRET / ENCRYPTION_KEY
- Ошибка: миграции Prisma падают при обновлении версии
- Ошибка: письма с подтверждением встречи не приходят
- Ошибка: календарь не синхронизируется с Google Calendar / Outlook
- Ошибка: видеозвонки Cal Video / Zoom / Google Meet не создаются
- Ошибка: медленная загрузка и таймауты за обратным прокси
- Ошибка: подключение к базе рвётся под нагрузкой
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Как обычно разворачивают Cal.com и где здесь ловушки
Официальный путь — docker-compose.yml из репозитория calcom/cal.com, который поднимает три сервиса: сам Cal.com (Next.js-приложение), PostgreSQL и, при желании, отдельный воркер для писем. Минимальный набор для теста:
services:
calcom:
image: calcom/cal.com:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://calcom:${DB_PASSWORD}@db:5432/calcom
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- CALENDSO_ENCRYPTION_KEY=${ENCRYPTION_KEY}
- NEXT_PUBLIC_WEBAPP_URL=https://cal.example.com
depends_on:
- db
db:
image: postgres:16
restart: unless-stopped
environment:
- POSTGRES_USER=calcom
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=calcom
volumes:
- calcom_db:/var/lib/postgresql/data
volumes:
calcom_db:
Ловушка в том, что это работает ровно до первого перезапуска, первого обновления образа или первого приглашения второго пользователя в команду. Дальше — по пунктам.
Ошибка: приложение падает при старте с ошибкой NEXTAUTH_SECRET / ENCRYPTION_KEY
Самая частая причина «белого экрана» или падения контейнера в первые секунды — не заданы (или заданы одинаковыми у нескольких сервисов) секретные переменные. Cal.com жёстко требует:
NEXTAUTH_SECRET— секрет для подписи сессий NextAuth;CALENDSO_ENCRYPTION_KEY— ключ шифрования (используется, в частности, для хранения токенов подключённых календарей).
Если переменная пустая, приложение либо не стартует, либо стартует, но ломается на первой попытке залогиниться. Генерировать оба значения нужно случайно, а не «для теста» вписывать 12345:
openssl rand -base64 32 # для NEXTAUTH_SECRET
openssl rand -hex 16 # для CALENDSO_ENCRYPTION_KEY
Держите оба значения в .env-файле рядом с docker-compose.yml, а не в самом compose-файле — так они не улетят в git по невнимательности:
# .env
DB_PASSWORD=сложный_пароль
NEXTAUTH_SECRET=сгенерированное_значение
ENCRYPTION_KEY=сгенерированное_значение
Важный нюанс: после первого успешного старта эти значения менять нельзя. Смена NEXTAUTH_SECRET разлогинит всех пользователей, а смена CALENDSO_ENCRYPTION_KEY сделает нечитаемыми уже сохранённые интеграции с внешними календарями — их придётся переподключать заново.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОшибка: миграции Prisma падают при обновлении версии
Cal.com использует Prisma как ORM, и при каждом релизе меняется схема БД. Если просто заменить тег образа с calcom/cal.com:4.8.0 на calcom/cal.com:latest и перезапустить контейнер, есть шанс словить одну из двух проблем:
- контейнер стартует, но часть функций падает с 500-й ошибкой — миграции не применились;
- контейнер вообще не поднимается, в логах —
P3009илиMigration failed to apply.
Правильный порядок обновления:
# 1. Бэкап базы перед любым обновлением — обязательно
docker exec -t $(docker ps -qf "name=db") \
pg_dump -U calcom calcom > backup_$(date +%F).sql
# 2. Тянем новый образ, но не запускаем
docker compose pull calcom
# 3. Останавливаем старый контейнер приложения
docker compose stop calcom
# 4. Применяем миграции явно, до запуска веб-процесса
docker compose run --rm calcom npx prisma migrate deploy
# 5. Запускаем обновлённое приложение
docker compose up -d calcom
Если миграция всё же упала на середине (частая причина — вручную изменённая схема или прерванный предыдущий деплой), Prisma помечает её как failed и блокирует все последующие миграции. Смотрите статус:
docker compose run --rm calcom npx prisma migrate status
Если миграция реально была применена вручную или её эффект уже не нужен, её можно пометить как отменённую (не откатывает данные, только снимает блокировку):
docker compose run --rm calcom npx prisma migrate resolve --rolled-back <имя_миграции>
Действовать так стоит только когда вы понимаете, что миграция сделала — вслепую резолвить миграции на проде не стоит, лучше поднять копию на тестовом окружении и прогнать обновление там.
Ошибка: письма с подтверждением встречи не приходят
Это, вероятно, самая частая жалоба на self-hosted Cal.com. Причин обычно три, и они разные.
1. Не настроен SMTP. Без переменных EMAIL_SERVER_HOST, EMAIL_SERVER_PORT, EMAIL_SERVER_USER, EMAIL_SERVER_PASSWORD и EMAIL_FROM Cal.com молча не отправляет письма — ошибки в интерфейсе вы не увидите, только в логах контейнера:
environment:
- EMAIL_FROM=booking@example.com
- EMAIL_SERVER_HOST=smtp.example.com
- EMAIL_SERVER_PORT=587
- EMAIL_SERVER_USER=booking@example.com
- EMAIL_SERVER_PASSWORD=${SMTP_PASSWORD}
2. Письма уходят, но падают в спам. Если вы держите свой почтовый сервер (например, iRedMail — см. настройку iRedMail на VPS), без корректных SPF, DKIM и обратной PTR-записи для IP сервера письма от Cal.com будут массово отбраковываться Gmail и Яндексом. Надёжнее для транзакционных писем — внешний SMTP-релей (Postmark, SendGrid, Mailgun и т.п.) с уже прогретой репутацией отправителя.
3. Письма зависают в очереди воркера. Часть уведомлений (напоминания, отмены) обрабатывает отдельный процесс — если не настроен cron-триггер для /api/cron/... эндпоинтов, задачи копятся необработанными. Логи таких крон-джобов проверяются так же, как и для любых других периодических задач на сервере — общие подходы разобраны в статье про частые ошибки cron-задач.
Ошибка: календарь не синхронизируется с Google Calendar / Outlook
Синхронизация календарей идёт через OAuth-приложения, которые вы регистрируете сами в Google Cloud Console или Azure AD, — и большинство проблем возникает именно тут, а не в самом Cal.com:
- redirect_uri_mismatch — в Google Cloud Console нужно точно указать
https://cal.example.com/api/integrations/googlecalendar/callback. Один лишний или отсутствующий слэш на конце — и авторизация не пройдёт. - Приложение в режиме Testing — Google OAuth-приложения без верификации работают только для email-адресов, явно добавленных в список тестовых пользователей. Если вы подключаете календарь с другого аккаунта — добавьте его в тестовые пользователи проекта в Google Cloud Console.
- Не включён нужный API — для Google Calendar в Cloud Console должен быть включён именно Google Calendar API (а не просто создан OAuth Client ID).
Переменные, которые нужно передать в контейнер после регистрации приложения:
environment:
- GOOGLE_API_CREDENTIALS={"web":{"client_id":"...","client_secret":"..."}}
- GOOGLE_LOGIN_ENABLED=true
Если после подключения календарь всё равно не видит занятые слоты — проверьте, что у OAuth-приложения запрошен scope calendar.readonly или шире, а не только calendar.events.
Ошибка: видеозвонки Cal Video / Zoom / Google Meet не создаются
Интеграции с видеосвязью — ещё одна точка отказа: встреча создалась, а ссылки на звонок нет.
- Zoom: нужен JWT- или OAuth-приложение в Zoom Marketplace с правильными scopes (
meeting:write), иZOOM_CLIENT_ID/ZOOM_CLIENT_SECRETдолжны быть заданы до того, как пользователь подключает интеграцию через UI — задним числом добавленные переменные требуют переподключения. - Cal Video (Daily.co) требует
DAILY_API_KEYиDAILY_SCALE_PLAN— без ключа Daily.co интеграция в списке будет, но при создании встречи будет падать с ошибкой на бэкенде. - Проверяйте логи именно в момент создания бронирования (
docker compose logs -f calcom), а не задним числом — часть таких ошибок Cal.com не показывает пользователю в интерфейсе, только пишет в stdout контейнера.
Ошибка: медленная загрузка и таймауты за обратным прокси
Cal.com — тяжёлое Next.js-приложение с server-side rendering, и за Nginx или Caddy оно иногда упирается в дефолтные таймауты, особенно на слабом сервере или при холодном старте после деплоя. Симптом — страница бронирования иногда отдаёт 502/504 в первые секунды после рестарта контейнера.
Пример конфига Nginx с увеличенными таймаутами и правильными заголовками для WebSocket (нужны для живых обновлений слотов):
server {
listen 443 ssl http2;
server_name cal.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
Если вы используете Caddy вместо Nginx, аналогичная настройка получается короче — сравнение подходов есть в статье Caddy или Nginx: что выбрать для сервера. А для самих проблем с автовыдачей SSL в Caddy отдельно разобраны частые случаи в Caddy с авто-SSL: частые ошибки и решения.
Отдельно проверьте ресурсы сервера: Cal.com на минимальной конфигурации (1 vCPU / 1 ГБ RAM) стартует, но под нагрузкой в несколько параллельных бронирований память быстро упирается в лимит, и Node.js-процесс начинает подтормаживать на сборке мусора. Комфортный минимум для продакшена — 2 vCPU и 2-4 ГБ RAM, отдельно под PostgreSQL стоит смотреть на диск: база растёт за счёт логов бронирований и журналов вебхуков быстрее, чем кажется на старте.
Ошибка: подключение к базе рвётся под нагрузкой
Если Cal.com используют несколько сотрудников одновременно и в логах приложения появляется too many connections или Can't reach database server, дело почти всегда в лимите подключений PostgreSQL, а не в самом Cal.com. По умолчанию max_connections в образе postgres:16 — 100, но Next.js-приложение с несколькими воркерами может держать пул на каждый процесс отдельно, и лимит выбирается быстрее, чем ожидаешь.
Решение — либо поставить перед PostgreSQL пулер соединений (PgBouncer в режиме transaction pooling), либо явно ограничить пул на стороне Prisma через connection_limit в DATABASE_URL:
DATABASE_URL=postgresql://calcom:pass@db:5432/calcom?connection_limit=10&pool_timeout=20
Если база вообще периодически отваливается — это уже общая тема настройки PostgreSQL, разобранная в статье про частые ошибки PostgreSQL — там же про shared_buffers, work_mem и типичные причины OOM у самой БД. Общие принципы стабильного docker-compose для прод-окружения (health-check, restart policy, лимиты ресурсов на контейнеры) — в статье Docker Compose для продакшена: частые ошибки и решения.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли развернуть Cal.com без Docker, напрямую через Node.js?
Можно — проект собирается через yarn и запускается через pm2 или systemd-юнит, но тогда вы вручную отвечаете за версию Node.js, PostgreSQL и все зависимости. Docker Compose проще поддерживать при обновлениях, потому что миграции и переменные окружения зафиксированы вместе с версией образа.
Нужен ли Redis для self-hosted Cal.com?
Для базовой установки — нет, но начиная с определённых версий он используется для rate limiting и кэша сессий при высокой нагрузке. Если у вас команда больше 10-15 человек с активным бронированием — стоит добавить redis:7-alpine и указать REDIS_URL.
Как перенести Cal.com на другой сервер без потери данных?
Снимите pg_dump с базы, перенесите .env с теми же NEXTAUTH_SECRET и CALENDSO_ENCRYPTION_KEY — без них расшифровать сохранённые токены календарей не получится, — и на новом сервере восстановите базу перед первым стартом приложения.
Стоит ли использовать latest тег образа в проде?
Нет. Фиксируйте конкретную версию (calcom/cal.com:4.9.2 и т.п.) и обновляйтесь осознанно, тестируя миграции на копии базы — так вы не поймаете чужой breaking change посреди рабочего дня.
Почему после обновления пропали кастомные event types или темы оформления?
Обычно это не «пропали», а не применились новые переменные окружения (NEXT_PUBLIC_... требуют пересборки образа, а не просто рестарта) — сверьте список переменных с CHANGELOG версии, на которую обновляетесь.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →