SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor

Настройка своего CalDAV-сервера на VPS с Radicale

Создайте личный сервер календарей для синхронизации устройств без участия Google. Инструкция по установке Radicale, настройке TLS и параметров discovery для корректной работы.

Что вы создаете

Собственный календарь — это CalDAV-сервер на VPS под вашим управлением, защищенный TLS, с отдельной учетной записью для каждого пользователя. Смартфон в вашем кармане и ноутбук на столе отображают одни и те же события, как и ноутбук вашего партнера. Учетная запись Google не выступает посредником.

Это задача, отличная от собственной страницы бронирования. Страница бронирования предназначена для посторонних: она публикует ваши свободные слоты и позволяет кому-либо занять один из них. Календарный сервер предназначен для ваших собственных устройств: он хранит события и синхронизирует все клиенты между собой. Часто пользователи запускают оба решения, и тогда инструмент бронирования считывает доступность из CalDAV-сервера, который вы создадите здесь.

Установка невелика. Radicale — это один пакет Python и около десяти строк конфигурации. То, что определяет, проработает ли установка первый месяц, — это TLS, обнаружение (discovery), коллекции для каждого пользователя и резервное копирование. Им уделено основное внимание ниже.

Что такое CalDAV и почему это важно?

CalDAV — это протокол синхронизации календарей поверх HTTP. Он определён в RFC 4791 как расширение WebDAV (web distributed authoring and versioning, набор дополнительных HTTP-методов, описанных в RFC 4918). Календарь представляет собой коллекцию, которая ведет себя как каталог. Одно событие — это отдельный файл внутри него, записанный в текстовом формате iCalendar (RFC 5545), том же самом, что используется для .ics вложений в вашей почте.

Клиенты используют обычный HTTP с несколькими дополнительными методами. PROPFIND запрашивает содержимое и свойства объекта. REPORT запрашивает отфильтрованный срез данных, например, все события в заданном диапазоне дат. PUT записывает событие, а DELETE удаляет его. Каждое событие содержит строку UID, и этот идентификатор позволяет двум устройствам «понимать», что они работают с одним и тем же событием, а не с его копией.

Преимущество заключается в переносимости, и именно ради этого стоит использовать протокол. iOS, macOS, Thunderbird, Evolution и Android (через DAVx⁵) поддерживают CalDAV. Ваши данные не привязаны к серверу, который вы выбрали сегодня. Перенесите файлы на другой CalDAV-сервер, укажите клиентам новое имя хоста, и всё остальное останется прежним.

CardDAV работает по схожему принципу. Это та же концепция для контактов, определённая в RFC 6352, где вместо событий хранятся файлы vCard. Каждый сервер, перечисленный ниже, поддерживает оба протокола для одной учётной записи, поэтому, как только вы настроите календарь, подключение адресной книги станет делом одной галочки.

Какой CalDAV-сервер выбрать?

Radicale — это максимально компактное решение. Оно написано на Python, не требует базы данных, а хранилищем служит обычная папка с файлами. В этом руководстве используется именно оно, так как для домашнего календаря большего не требуется, а вероятность возникновения критических ошибок в три часа ночи минимальна.

Baikal — вариант с веб-интерфейсом администратора. Он работает на PHP с использованием библиотеки sabre/dav, хранит пользователей и календари в SQLite или MySQL и позволяет добавлять новых пользователей через браузер, а не через командную строку. Выбирайте его, если учетные записи создаются и удаляются часто.

Nextcloud подходит, если календарь — лишь одна из многих функций. Вы получаете календарь, контакты, файловое хранилище и мобильное приложение, но взамен придется обслуживать PHP-FPM, базу данных и планировщик фоновых задач. Если это кажется избыточным для ваших задач, ознакомьтесь с более легкими альтернативами Nextcloud, а самостоятельно хостируемая синхронизация файлов поможет решить вторую половину задач, ради которых обычно устанавливают Nextcloud.

DAViCal — проверенный временем вариант на базе PostgreSQL. Его стоит рассматривать только в том случае, если вы уже используете PostgreSQL и хотите хранить данные календарей именно в этой СУБД.

Установка Radicale в Ubuntu 24.04

Radicale 3.8.3, выпущенный 8 октября 2026 года, является актуальной версией на 11 октября 2026 года. Установите его в отдельное виртуальное окружение с дополнением bcrypt, от которого зависит приведенный ниже файл паролей.

sudo apt update
sudo apt install -y python3-venv apache2-utils nginx
sudo useradd --system --user-group --home-dir / --shell /usr/sbin/nologin radicale
sudo install -d -o radicale -g radicale -m 750 /var/lib/radicale/collections
sudo install -d -m 750 -o root -g radicale /etc/radicale
sudo python3 -m venv /opt/radicale/venv
sudo /opt/radicale/venv/bin/pip install --upgrade 'radicale[bcrypt]'

Использование виртуального окружения — это не вопрос стиля. sudo pip install radicale в системный Python завершается ошибкой error: externally-managed-environment, так как Ubuntu помечает системный Python как управляемый через apt, чтобы pip не мог перезаписать пакетные файлы.

Создайте /etc/radicale/config:

[server]
hosts = 127.0.0.1:5232

[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = autodetect

[storage]
filesystem_folder = /var/lib/radicale/collections

hosts намеренно привязывается к loopback. nginx выполняет TLS termination и пересылает запросы на этот порт, поэтому Radicale никогда не взаимодействует с Интернетом напрямую. Пример вышестоящего прокси 0.0.0.0:5232 публикует незашифрованный сервис, принимающий пароли; это критическая ошибка, которой следует избегать.

Теперь учетные записи. -B выбирает bcrypt, который Radicale считывает с помощью htpasswd_encryption = autodetect при наличии модуля bcrypt из установки radicale[bcrypt], выполненной выше:

sudo htpasswd -B -c /etc/radicale/users you
sudo htpasswd -B /etc/radicale/users partner
sudo chown root:radicale /etc/radicale/users
sudo chmod 640 /etc/radicale/users

-c создает файл и удаляет всё его содержимое. Используйте эту команду только для первого пользователя. Повторный запуск htpasswd -B -c спустя время удалит все учетные записи, добавленные после первой. Симптомом будет ситуация, когда один пользователь успешно синхронизируется, а у остальных бесконечно запрашивается пароль. SHA-512 crypt (-5) здесь не подходит: поддержка SHA-2 появилась в htpasswd в Apache 2.4.59, а в Ubuntu 24.04 поставляется apache2-utils 2.4.58, утилита htpasswd в которой не имеет флага -5.

Создайте /etc/systemd/system/radicale.service, адаптировав его из юнита в документации Radicale:

[Unit]
Description=CalDAV and CardDAV server
After=network.target
Requires=network.target

[Service]
ExecStart=/opt/radicale/venv/bin/python -m radicale
Restart=on-failure
User=radicale
UMask=0027
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
NoNewPrivileges=true
ReadWritePaths=/var/lib/radicale/

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now radicale
curl -i http://127.0.0.1:5232/

Корректный результат — 401 Unauthorized с заголовком WWW-Authenticate: сервис прослушивает порт, аутентификация включена. Connection refused означает, что по этому адресу ничего не слушается, обычно из-за того, что сервис не запустился или завершил работу, а journalctl -u radicale -n 50 указывает на отклоненную опцию. ReadWritePaths=/var/lib/radicale/ — это строка, которая позволяет сервису сохранять события, так как ProtectSystem=strict монтирует файловую систему в режиме только для чтения для этого сервиса. Если удалить эту строку, чтение будет работать, но любая запись завершится ошибкой.

TLS обязателен, так как клиенты отклоняют открытый текст

Данная конфигурация использует аутентификацию HTTP Basic — единственный метод, поддерживаемый Radicale. При этом Basic передает user:password в кодировке base64 с каждым запросом. Base64 — это кодировка, а не шифрование. При использовании обычного HTTP пароль передается через все сети между телефоном и сервером при каждой синхронизации.

Клиенты принудительно требуют TLS. В документации Radicale указано, что macOS Calendar.app может молча отклонять отправку учетных данных по незащищенному HTTP, аналогично ведет себя и iOS. Учетная запись выглядит настроенной, но синхронизация не происходит, при этом никаких сообщений об ошибках не выводится.

Сначала направьте A-запись для cal.example.com на ваш VPS, так как центр сертификации выполняет проверку домена. Затем создайте /etc/nginx/sites-available/cal.example.com:

server {
    listen 80;
    server_name cal.example.com;

    location / {
        proxy_pass        http://localhost:5232/;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_set_header  Host $http_host;
        proxy_pass_header Authorization;
    }

    location = /.well-known/caldav  { return 301 https://$host/; }
    location = /.well-known/carddav { return 301 https://$host/; }
}

Четыре строки с заголовками прокси взяты из документации Radicale. Оставьте их без изменений.

sudo ln -s /etc/nginx/sites-available/cal.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d cal.example.com
curl -i -u you https://cal.example.com/

nginx -t выводит syntax is ok и test is successful. Выполняйте перезагрузку только после успешной проверки, так как перезагрузка с некорректным файлом сохранит старую конфигурацию и скроет ошибку до следующего перезапуска. Certbot редактирует файл конфигурации сайта на месте: он устанавливает сертификат, переключает блок на порт 443 и добавляет редирект с порта 80. Финальная команда curl запросит пароль и должна вернуть 200 — это веб-интерфейс Radicale. Ошибка 502 Bad Gateway означает, что nginx запущен, но не получил ответа от порта 5232; обычно это происходит, если Radicale не слушает этот порт.

Почему добавление учетной записи на телефоне завершается ошибкой?

Причина кроется в механизме обнаружения. RFC 6764 описывает, как клиент преобразует имя хоста в URL календаря. Он ищет SRV-запись _caldavs._tcp, затем запрашивает https://cal.example.com/.well-known/caldav и ожидает перенаправления на DAV-корень. Оттуда он запрашивает current-user-principal, затем calendar-home-set для этого субъекта, и только после этого обнаруживает ваши календари. Телефон предоставляет только одно поле для ввода сервера, поэтому каждый этап должен выполняться автоматически.

curl -sI https://cal.example.com/.well-known/caldav

Корректным ответом является HTTP/2 301 с заголовком location: https://cal.example.com/. Ошибка 404 в этом ответе — частая причина, по которой iOS сообщает о невозможности проверки учетной записи, в то время как Thunderbird в той же сети работает корректно: Thunderbird использует полный URL, который вы ввели, поэтому ему не требуется перенаправление.

Цель перенаправления зависит от сервера. Radicale, запущенный в корне сайта, перенаправляет на /, и Radicale сам отвечает по этому пути кодом 301, когда запрос достигает его, поэтому правила nginx делают перенаправление явным, а не создают его. Baikal поставляется с примерами правил, которые перенаправляют на /dav.php с кодом состояния 308. Nextcloud перенаправляет на /remote.php/dav/.

Создание календарей и предоставление общего доступа

Многие клиенты не умеют создавать календари, а могут только подписываться на существующие. Откройте https://cal.example.com/ в браузере, войдите под учетной записью you и создайте календарь там. На диске он будет размещен по пути /var/lib/radicale/collections/collection-root/you/, где имя папки представляет собой сгенерированный идентификатор.

Механизм прав доступа по умолчанию в Radicale — это owner_only: аутентифицированная учетная запись может читать и записывать только свои собственные коллекции в /USERNAME/. Для большинства домашних пользователей это оптимальная настройка. Самый простой способ организовать общий доступ — создать третью учетную запись. Создайте household с помощью htpasswd, создайте общий календарь под этим логином и добавьте его на каждом устройстве как вторую учетную запись CalDAV. Это работает на любом клиенте, включая iOS, так как календарь находится в домашней директории этой учетной записи.

Если требуется более гибкое управление, переключитесь на права доступа на основе правил. Добавьте следующее в /etc/radicale/config:

[rights]
type = from_file
file = /etc/radicale/rights

Затем настройте /etc/radicale/rights, опираясь на пример из документации Radicale:

[root]
user: .+
collection:
permissions: R

[principal]
user: .+
collection: {user}
permissions: RW

[own-calendars]
user: .+
collection: {user}/[^/]+
permissions: rw

[shared-household]
user: you|partner
collection: you/2f0a9c1e-1f4c-4c2b-9a1b-0d2f7a5c9e11
permissions: rw

Заглавные и строчные буквы имеют разное значение. R и W позволяют читать и записывать коллекции, которые не являются календарями или адресными книгами (это так называемые principal-папки). r и w отвечают за чтение и запись самих календарей. Замените этот идентификатор на реальное имя папки вашего календаря из пути хранения, указанного выше.

Важное ограничение: клиент, который считывает только домашнюю директорию календарей пользователя, не отобразит календарь, находящийся в пути другого пользователя, так как механизм обнаружения не переходит по таким ссылкам. Thunderbird и DAVx⁵ могут добавить такой календарь по полному URL. iOS этого не умеет, поэтому схема с общей учетной записью является единственным универсальным решением.

Настройка клиентов: здесь чаще всего терпят неудачу self-hosted календари

iPhone и iPad. Откройте «Настройки», затем «Календарь» (в новых версиях iOS находится в разделе «Приложения»), далее «Учетные записи», «Добавить учетную запись», «Другое», «Добавить учетную запись CalDAV». В поле «Сервер» введите cal.example.com, затем укажите имя пользователя и пароль. Описание — это просто метка. Если сохранить настройки не удается, откройте учетную запись снова: в расширенном представлении доступны параметры «Использовать SSL», порт и полный URL учетной записи. Вставка полного URL позволяет полностью пропустить этап автоматического обнаружения.

Android. В системе нет встроенного клиента CalDAV. Установите DAVx⁵ из F-Droid или Google Play, добавьте учетную запись, используя базовый URL https://cal.example.com/ и ваше имя пользователя, затем отметьте нужные календари. DAVx⁵ записывает данные в системный календарный провайдер Android, поэтому события появятся в любом приложении-календаре, которое вы используете.

Thunderbird. «Новый календарь», «В сети», затем введите имя пользователя и адрес https://cal.example.com/. Приложение покажет найденные календари и предложит выбрать те, которые нужно добавить.

macOS. «Системные настройки», «Учетные записи в Интернете», «Добавить другую учетную запись», «CalDAV», установите тип учетной записи «Вручную», затем введите те же имя пользователя, пароль и адрес сервера.

CalDAV — это протокол опроса. В спецификации нет поддержки push-уведомлений, поэтому событие, добавленное на ноутбуке, появится на телефоне не мгновенно, а во время следующей синхронизации. Установите интервал, который вас устраивает, но помните: слишком частая синхронизация на телефоне быстрее расходует заряд аккумулятора.

Резервное копирование хранилища, состоящего только из файлов

В Radicale ваш календарь представляет собой директорию с .ics файлами — по одному файлу на каждое событие, плюс небольшой файл свойств для каждой коллекции. Любой инструмент, копирующий директории, подходит для создания резервной копии. Вы можете открыть резервную копию с помощью less, чтобы убедиться, что она содержит реальные события. Это существенное преимущество перед дампом базы данных, который невозможно прочитать напрямую.

sudo systemctl stop radicale
sudo tar czf /root/radicale-$(date +%F).tar.gz -C /var/lib/radicale collections
sudo systemctl start radicale

Остановите сервис на несколько секунд, пока создается архив, чтобы ни один клиент не находился в процессе записи в момент чтения файлов. После этого скопируйте архив за пределы сервера, так как резервная копия на том же VPS не поможет при сбое, к которому вы готовитесь. Восстановление выполняется в обратном порядке: распаковка, sudo chown -R radicale:radicale /var/lib/radicale/collections, запуск сервиса. Каждый клиент также хранит локальную копию своих календарей, поэтому ноутбук, который не синхронизировался с момента сбоя, является второй копией ваших данных.

Когда лучше выбрать Baikal, а когда Nextcloud

Версия Baikal 0.12.1 была выпущена 5 августа 2026 года и требует PHP 8.2 или новее. Распакуйте её вне корневой директории веб-сервера и предоставьте доступ только к директории html:

sudo apt install -y php-fpm php-sqlite3 php-xml php-mbstring php-curl unzip
cd /tmp
curl -LO https://github.com/sabre-io/Baikal/releases/download/0.12.1/baikal-0.12.1.zip
sudo unzip -q baikal-0.12.1.zip -d /srv
sudo chown -R www-data:www-data /srv/baikal/Specific /srv/baikal/config

Эти две директории — единственные, в которые веб-сервер должен иметь право записи, поэтому остальные файлы не требуют прав на запись. Внутри вашего блока server в nginx специфические настройки для Baikal выглядят так:

root /srv/baikal/html;
index index.php;

location ~ /(\.ht|Core|Specific|config) { deny all; }

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

location = /.well-known/caldav  { return 308 /dav.php; }
location = /.well-known/carddav { return 308 /dav.php; }

Перезагрузите nginx, откройте сайт в браузере, и мастер настройки создаст учётную запись администратора и базу данных SQLite. Настройка клиента идентична настройке для Radicale, где https://cal.example.com/ выступает в качестве адреса сервера, так как правило well-known перенаправляет процесс обнаружения на /dav.php.

Nextcloud оправдывает свою ресурсоёмкость только в том случае, если вам также нужны хранилище файлов и мобильное приложение под одной учётной записью. Его DAV-корень находится по адресу /remote.php/dav/, и к нему применяются те же правила обнаружения. Для любого из этих решений запуск сервиса в контейнере позволит избежать конфликтов версий PHP в основной системе: Docker Compose на VPS описывает файл compose и работу reverse proxy перед ним, а что стоит хостить самостоятельно в 2026 году — подходящий материал, чтобы решить, насколько глубоко вы хотите погрузиться в этот процесс.

Типовые сбои и сообщения об ошибках

Каждая синхронизация возвращает 401. Либо файл паролей потерял учетные записи из-за второго htpasswd -c, либо пользователь radicale не может его прочитать. Проверьте с помощью sudo -u radicale cat /etc/radicale/users; ошибка permission denied укажет на проблему, решение — группа radicale с правами 640. Radicale по умолчанию делает паузу в одну секунду после каждой неудачной попытки входа, поэтому клиент с устаревшим паролем кажется медленным, а не отклоненным.

nginx отвечает 405 на PROPFIND. URL обрабатывается как статический файл, поэтому метод WebDAV не доходит до Radicale. Проверьте эндпоинт напрямую:

curl -u you -X PROPFIND -H "Depth: 0" -i https://cal.example.com/you/

Рабочая коллекция DAV отвечает 207 Multi-Status. Любой другой ответ означает, что запрос был остановлен на уровне веб-сервера.

Телефон не может подтвердить учетную запись, а браузер работает корректно. Две типичные причины. Отсутствует well-known редирект, что проверяется командой curl выше. Либо цепочка сертификатов неполная: браузеры скрывают это, самостоятельно догружая недостающий промежуточный сертификат, в то время как iOS этого не делает. Проверьте из командной строки:

openssl s_client -connect cal.example.com:443 -servername cal.example.com </dev/null

Ищите Verify return code: 0 (ok). Если проверка не проходит, конфигурация nginx указывает на cert.pem, хотя должна указывать на fullchain.pem.

Дубликаты событий после импорта. Каждое событие имеет UID, который клиенты используют для идентификации. Если импортировать один и тот же файл дважды через инструмент, который пересоздает идентификаторы, появятся два события, которые невозможно объединить. Удалите лишние копии на одном устройстве и позвольте удалению синхронизироваться.

Все перестает работать после перезагрузки. Сервис был запущен вручную. sudo systemctl is-enabled radicale выводит disabled, а sudo systemctl enable --now radicale исправляет это окончательно.

FAQ

Действительно ли мне нужен TLS для self-hosted CalDAV-сервера?

Да. Radicale использует аутентификацию HTTP Basic, поэтому пароль передаётся в кодировке base64 при каждом запросе, а base64 легко декодируется. Клиенты также требуют TLS: macOS Calendar.app может без уведомлений отказаться отправлять учётные данные по незащищённому HTTP, аналогично ведёт себя и iOS, из-за чего учётная запись кажется сохранённой, но синхронизация не происходит. sudo certbot --nginx -d cal.example.com — это всё, что требуется для решения задачи.

Почему телефон не может добавить учётную запись, хотя Thunderbird работает?

Thunderbird использует полный URL, который вы ввели. Телефон предоставляет только одно поле для сервера, поэтому он полагается на механизм обнаружения согласно RFC 6764. Если SRV-запись отсутствует, телефон запрашивает https://cal.example.com/.well-known/caldav и ожидает редирект на корень DAV. Если этот запрос возвращает 404, телефон обычно сообщает, что не может проверить учётную запись. Добавьте location = /.well-known/caldav { return 301 https://$host/; } в nginx, затем убедитесь с помощью curl -sI https://cal.example.com/.well-known/caldav, что вы получаете 301 и заголовок location. Вставка полного URL учётной записи в расширенных настройках iOS позволяет полностью пропустить этап обнаружения.

Могут ли два человека использовать один календарь?

Да, и самый надёжный способ — это общая учётная запись. Создайте третьего пользователя с помощью htpasswd, разместите общий календарь в его директории и добавьте его как вторую CalDAV-учётную запись на каждом устройстве. Файл прав доступа Radicale позволяет предоставить именованному пользователю права на чтение и запись в коллекции другого пользователя, но клиент, который считывает только свой собственный «home set» календарей, никогда его не отобразит. Поэтому такой путь подходит для Thunderbird и DAVx⁵, но не для iOS.

Что произойдёт с моими событиями, если VPS выйдет из строя?

В Radicale хранилище представляет собой обычный текст: один файл .ics на каждое событие в директории /var/lib/radicale/collections/collection-root/. Вы можете создать резервную копию с помощью tar и прочитать данные через less. Восстановление заключается в извлечении файлов, chown -R radicale:radicale и запуске службы. Каждый синхронизированный клиент также хранит локальную копию, поэтому ноутбук, который был синхронизирован до сбоя, содержит полную вторую копию вашего календаря.

Синхронизирует ли CalDAV-сервер также мои контакты?

Контакты используют CardDAV — родственный протокол, определённый в RFC 6352, который хранит файлы vCard вместо событий. Radicale, Baikal и Nextcloud обслуживают его из той же учётной записи и с того же имени хоста. На Android приложение DAVx⁵ синхронизирует календари и контакты из одной учётной записи. На iOS вы добавляете вторую учётную запись типа CardDAV с теми же данными, поэтому редирект /.well-known/carddav должен находиться в вашей конфигурации nginx рядом с редиректом для CalDAV.

#caldav#calendar#radicale#self-hosting#sync