SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor

Cómo montar un servidor CalDAV en un VPS

Sincroniza calendarios entre móvil y portátil sin Google. Configura Radicale en tu VPS con TLS, descubrimiento, cuentas separadas y clientes compatibles.

Lo que va a crear

Un calendario autogestionado es un único servidor CalDAV en un VPS que controla, protegido mediante TLS y con una cuenta independiente para cada persona. El teléfono que lleva consigo y el portátil de su escritorio muestran los mismos eventos, al igual que el portátil de su pareja. No hay ninguna cuenta de Google entre ellos.

Esta tarea es diferente de una página de reservas autogestionada. Una página de reservas está destinada a personas externas: publica sus horarios disponibles y permite que alguien reserve uno. Un servidor de calendario está destinado a sus propios dispositivos: almacena los eventos y mantiene todos los clientes sincronizados. Muchas personas ejecutan ambos sistemas, y la herramienta de reservas obtiene entonces su disponibilidad del servidor CalDAV que va a crear aquí.

La instalación es pequeña. Radicale es un paquete de Python y requiere unas diez líneas de configuración. Lo que determina si la configuración seguirá funcionando después del primer mes es TLS, el descubrimiento, las colecciones independientes por usuario y las copias de seguridad. A estos aspectos se dedica la mayor parte del contenido siguiente.

¿Qué es CalDAV y por qué es importante?

CalDAV permite sincronizar calendarios mediante HTTP. Está definido en RFC 4791 como un conjunto de extensiones para WebDAV (creación, edición y versionado distribuidos en la web; un conjunto de métodos HTTP adicionales definidos en RFC 4918). Un calendario es una colección que se comporta como un directorio. Cada evento es un archivo dentro de ella, escrito en el formato de texto iCalendar (RFC 5545), el mismo formato que los archivos adjuntos .ics de su correo.

Los clientes usan HTTP normal con algunos métodos adicionales. PROPFIND consulta qué elementos hay y qué propiedades tienen. REPORT solicita un subconjunto filtrado, como todos los eventos de un intervalo de fechas. PUT escribe un evento y DELETE lo elimina. Cada evento contiene una línea UID. Ese identificador permite que dos dispositivos sepan que están consultando el mismo evento y no una copia.

La portabilidad es la principal ventaja y la razón para usarlo. iOS, macOS, Thunderbird, Evolution y Android mediante DAVx⁵ son compatibles con CalDAV. Sus datos no quedan vinculados al servidor que elija hoy. Mueva los archivos a otro servidor CalDAV, configure los clientes con el nuevo nombre de host y no tendrá que cambiar nada más.

CardDAV funciona de forma similar. Es la misma idea aplicada a los contactos, está definido en RFC 6352 y almacena archivos vCard en lugar de eventos. Todos los servidores siguientes ofrecen ambos protocolos con la misma cuenta. Cuando el calendario funciona, la libreta de direcciones sólo requiere activar una opción.

¿Qué servidor CalDAV debe ejecutar?

Radicale es la opción mínima que funciona. Está escrito en Python, no usa una base de datos y almacena los datos en una carpeta con archivos de texto sin formato. Esta guía lo utiliza porque un calendario doméstico no necesita más y porque hay muy pocas cosas que puedan fallar a las tres de la mañana.

Baikal es la opción con un panel de administración web. Se ejecuta con PHP y la biblioteca sabre/dav, almacena los usuarios y calendarios en SQLite o MySQL y permite añadir una persona desde el navegador en lugar de usar la línea de comandos. Elíjalo cuando las cuentas cambien con frecuencia.

Nextcloud es adecuado cuando el calendario es una función entre varias. Incluye calendario, contactos, archivos y una aplicación móvil, a cambio de usar PHP-FPM, una base de datos y un ejecutor de tareas en segundo plano. Si esto parece demasiado para lo que realmente necesita, las alternativas más ligeras a Nextcloud cubren esa diferencia, y la sincronización de archivos autoalojada cubre la otra función para la que muchas personas instalan Nextcloud.

DAViCal es la opción de PostgreSQL con más trayectoria. Sólo merece la pena considerarlo si ya ejecuta PostgreSQL y quiere almacenar los datos del calendario en él.

Instalar Radicale en Ubuntu 24.04

Radicale 3.5.10 era la versión actual en agosto de 2026. Instálelo en su propio entorno virtual.

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

El entorno virtual no es una cuestión de estilo. sudo pip install radicale en el Python del sistema falla con error: externally-managed-environment, porque Ubuntu marca su Python como propiedad de apt para impedir que pip sobrescriba archivos de paquetes.

Escriba /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 se vincula intencionadamente a loopback. nginx termina TLS y reenvía las conexiones a ese puerto, por lo que Radicale nunca queda expuesto directamente a Internet. El ejemplo original de 0.0.0.0:5232 publica un servicio sin cifrado que acepta contraseñas. Ese es el error importante que debe evitarse.

Ahora cree las cuentas. -5 selecciona SHA-512 crypt, que Radicale lee con htpasswd_encryption = autodetect y sin ningún módulo adicional:

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

-c crea el archivo y trunca todo su contenido. Úselo sólo para el primer usuario. Si vuelve a ejecutar htpasswd -5 -c meses después, elimina todas las cuentas añadidas posteriormente. El síntoma es que una persona sincroniza correctamente, mientras las demás reciben una solicitud de contraseña que nunca termina. Bcrypt también funciona y necesita la instalación adicional radicale[bcrypt].

Cree /etc/systemd/system/radicale.service, adaptado de la unidad incluida en la documentación de 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/

Un resultado correcto es 401 Unauthorized con una cabecera WWW-Authenticate: el servicio está escuchando y la autenticación está activada. Connection refused significa que nunca se inició, y journalctl -u radicale -n 50 indica la opción que rechazó. ProtectSystem=strict monta el sistema de archivos como de solo lectura para este servicio, por lo que ReadWritePaths=/var/lib/radicale/ es la línea que le permite guardar un evento. Si elimina esa línea, las lecturas siguen funcionando, pero todas las escrituras fallan.

TLS no es opcional porque los clientes rechazan el texto sin cifrar

CalDAV se autentica mediante HTTP Basic, que envía user:password codificado en base64 en cada petición. Base64 es una codificación, no un cifrado. Con HTTP sin cifrar, la contraseña queda expuesta a cada red entre el teléfono y el servidor durante todo el día, en cada sincronización.

Los clientes lo impiden por usted. La documentación de Radicale indica que macOS Calendar.app puede negarse silenciosamente a enviar credenciales mediante HTTP no seguro, y iOS se comporta igual. La cuenta parece configurada, pero nunca se sincroniza y no aparece ningún error que consultar.

Cree primero un registro A para cal.example.com que apunte al VPS, porque la entidad certificadora lo comprueba. Después, cree /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/; }
}

Las cuatro líneas de cabeceras del proxy proceden de la documentación de Radicale. Manténgalas sin cambios.

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 muestra syntax is ok y test is successful. Recargue la configuración sólo después de que lo haga, porque una recarga con un archivo defectuoso mantiene activa la configuración anterior y oculta el error hasta el siguiente reinicio. Certbot modifica el archivo del sitio directamente: instala el certificado, cambia el bloque al puerto 443 y añade una redirección desde el puerto 80. El último comando curl solicita la contraseña y debería devolver 200, que es la propia interfaz web de Radicale. Un 502 Bad Gateway significa que nginx está activo y que Radicale no está escuchando en 5232.

¿Por qué falla al añadir la cuenta en un teléfono?

Se debe al descubrimiento. RFC 6764 describe cómo un cliente convierte un nombre de host en una URL de calendario. Busca un registro SRV _caldavs._tcp, después solicita https://cal.example.com/.well-known/caldav y espera una redirección a la raíz DAV. A continuación solicita current-user-principal y después calendar-home-set del principal correspondiente. Sólo entonces puede ver los calendarios. Un teléfono proporciona un único campo para el servidor, por lo que todos los pasos deben funcionar sin intervención.

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

La respuesta correcta es HTTP/2 301 con una cabecera location: https://cal.example.com/. La presencia de un 404 en ese punto hace que iOS indique que no puede verificar la información de la cuenta, mientras Thunderbird funciona en la misma red. Thunderbird utiliza la URL completa que se introdujo, por lo que no necesita la redirección.

El destino de la redirección depende del servidor. Radicale, cuando se sirve en la raíz del sitio, redirige a /. Baikal incluye reglas de ejemplo que redirigen a /dav.php con el estado 308. Nextcloud redirige a /remote.php/dav/.

Cree los calendarios y comparta uno con su pareja

Muchos clientes no pueden crear un calendario y sólo permiten suscribirse a uno. Abra https://cal.example.com/ en un navegador, inicie sesión como you y cree allí el calendario. En el disco se almacena en /var/lib/radicale/collections/collection-root/you/, con un identificador generado como nombre de la carpeta.

El backend de derechos predeterminado de Radicale es owner_only: una cuenta autenticada lee y escribe sus propias colecciones en /USERNAME/, y nada más. Para la mayoría de los hogares, esta es la configuración correcta. La forma más sencilla de compartir un calendario es usar una tercera cuenta. Cree household con htpasswd, cree el calendario compartido con esa cuenta y añádalo en cada dispositivo como una segunda cuenta CalDAV. Funciona en todos los clientes, incluido iOS, porque el calendario se encuentra en el directorio propio de esa cuenta.

Si necesita un control más preciso, cambie a derechos basados en reglas. Añada lo siguiente a /etc/radicale/config:

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

Después, /etc/radicale/rights, basándose en el ejemplo de la documentación de 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

Las letras mayúsculas y minúsculas tienen significados distintos. R y W leen y escriben colecciones que no son calendarios ni libretas de direcciones, que es lo que contiene una carpeta principal. r y w leen y escriben los calendarios. Sustituya ese identificador por el nombre real de la carpeta de su calendario, obtenido de la ruta de almacenamiento anterior.

Hay una limitación importante: un cliente que sólo lee el conjunto de calendarios de la cuenta no mostrará un calendario ubicado en la ruta de otro usuario, porque el descubrimiento no recorre esa ruta. Thunderbird y DAVx⁵ pueden añadirlo mediante la URL completa. iOS no puede hacerlo. Por eso el patrón de la cuenta compartida es el que siempre funciona.

Configure los clientes, porque aquí es donde suelen fallar los calendarios autohospedados

iPhone y iPad. Abra Ajustes y seleccione Calendario (en las versiones recientes de iOS está dentro de Apps), Cuentas de calendario, Añadir cuenta, Otra y Añadir cuenta CalDAV. El servidor es cal.example.com, seguido del nombre de usuario y la contraseña. La descripción sólo es una etiqueta. Si no permite guardar la cuenta, ábrala de nuevo: la vista avanzada muestra Usar SSL, el puerto y la URL completa de la cuenta. Si pega la URL, omitirá por completo la detección automática.

Android. No incluye un cliente CalDAV. Instale DAVx⁵ desde F-Droid o Google Play y añada una cuenta con la URL base https://cal.example.com/ y su nombre de usuario. Después, marque los calendarios que quiera usar. DAVx⁵ escribe en el proveedor de calendarios de Android, por lo que los eventos aparecen en la aplicación de calendario que ya utilice.

Thunderbird. Seleccione Nuevo calendario, En la red y, después, introduzca su nombre de usuario y la ubicación https://cal.example.com/. Thunderbird muestra lo que ha encontrado y pregunta qué calendarios quiere añadir.

macOS. Abra Ajustes del Sistema, Cuentas de Internet, Añadir otra cuenta y CalDAV. Establezca Tipo de cuenta en Manual y, después, introduzca el mismo nombre de usuario, la contraseña y la dirección del servidor.

CalDAV es un protocolo de consulta periódica. La especificación no incluye notificaciones push, por lo que un evento que añada en el portátil llega al teléfono durante la siguiente sincronización, no en el mismo segundo. Configure en cada cliente un intervalo que le resulte adecuado y recuerde que un intervalo más corto en el teléfono consume más batería.

Haga una copia de seguridad del almacén, que sólo contiene archivos

En Radicale, el calendario es un directorio de archivos .ics, con un archivo por evento, además de un archivo pequeño de propiedades por colección. Cualquier herramienta que copie un directorio crea una copia de seguridad. Puede abrir una copia con less para confirmar que contiene eventos reales. Esta es una ventaja importante frente a un volcado de base de datos que no se puede leer.

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

Detenga el servicio durante los pocos segundos que tarda el archivado. Así ningún cliente estará realizando una escritura a medias mientras se leen los archivos. Después, copie el archivo fuera del servidor. Una copia de seguridad en el mismo VPS no sobrevive al fallo que intenta evitar. La restauración se realiza en sentido inverso: extraiga los archivos, ejecute sudo chown -R radicale:radicale /var/lib/radicale/collections y vuelva a iniciar el servicio. Además, cada cliente conserva una copia local de sus calendarios. Por tanto, un portátil que no se haya sincronizado desde el fallo contiene una segunda copia de los datos.

Cuándo Baikal o Nextcloud son la mejor opción

Baikal 0.12.1 se publicó el 5 de agosto de 2026 y necesita PHP 8.2 o posterior. Descomprímalo fuera de la raíz web y exponga sólo su directorio 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

Esos dos directorios son los únicos en los que el servidor web escribe, por lo que no es necesario que ningún otro tenga permisos de escritura. Dentro del bloque de servidor de nginx, las partes específicas de Baikal son estas:

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; }

Recargue nginx, abra el sitio en un navegador y el asistente de configuración creará la cuenta de administrador y la base de datos SQLite. La configuración del cliente es idéntica a la de Radicale, con https://cal.example.com/ como dirección del servidor, porque la regla well-known envía el descubrimiento a /dav.php.

Nextcloud sólo compensa si también quiere archivos y una aplicación para el teléfono con el mismo inicio de sesión. Su raíz DAV es /remote.php/dav/ y se aplican las mismas reglas de descubrimiento. Para cualquiera de estas opciones, ejecutar el servicio en un contenedor evita tener versiones de PHP en el host: Docker Compose en un VPS explica el archivo compose y el reverse proxy situado delante, y qué merece la pena alojar por cuenta propia en 2026 es un buen lugar para decidir hasta dónde quiere seguir este camino.

Modos de fallo y cadenas que verá

Todas las sincronizaciones devuelven 401. El archivo de contraseñas perdió sus cuentas debido a un segundo htpasswd -c, o el usuario radicale no puede leerlo. Compruébelo con sudo -u radicale cat /etc/radicale/users; si aparece un error de permisos, esa es la causa. Corríjalo con el grupo radicale y el modo 640. Radicale también espera un segundo después de cada inicio de sesión fallido de forma predeterminada. Por eso, un cliente con una contraseña obsoleta parece lento en lugar de mostrar un rechazo inmediato.

nginx responde 405 a PROPFIND. La URL se está sirviendo como un archivo estático. Por eso, el método WebDAV nunca llega a Radicale. Pruebe el endpoint directamente:

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

Una colección DAV operativa responde 207 Multi-Status. Cualquier otro resultado significa que la petición se detuvo en el servidor web.

El teléfono no puede verificar la cuenta, pero el navegador funciona. Hay dos causas habituales. Falta la redirección well-known, que se comprueba con el comando curl anterior. O la cadena de certificados está incompleta. Los navegadores suelen resolverlo descargando el certificado intermedio que falta, pero iOS no lo hace. Compruébelo desde el shell:

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

Busque Verify return code: 0 (ok). Si falla, la configuración de nginx apunta a cert.pem cuando debería apuntar a fullchain.pem.

Hay eventos duplicados después de una importación. Cada evento contiene un UID, y los clientes lo tratan como su identidad. Si importa dos veces el mismo archivo con una herramienta que vuelve a generar los identificadores, obtendrá dos eventos que nunca se fusionarán. Elimine las copias adicionales en un dispositivo y deje que la eliminación se sincronice.

Todo deja de funcionar después de reiniciar. El servicio se inició manualmente. sudo systemctl is-enabled radicale muestra disabled, y sudo systemctl enable --now radicale corrige el problema de forma permanente.

FAQ

¿Realmente necesito TLS para un servidor CalDAV autogestionado?

Sí. CalDAV se autentica mediante HTTP Basic, por lo que la contraseña viaja codificada en base64 en cada solicitud. base64 se puede revertir de forma trivial. Los clientes también lo exigen: macOS Calendar.app puede negarse silenciosamente a enviar credenciales mediante HTTP no seguro, e iOS se comporta de la misma forma. Por eso la cuenta parece guardarse, pero nunca se sincroniza. sudo certbot --nginx -d cal.example.com realiza todo el trabajo.

¿Por qué mi teléfono no puede añadir la cuenta si Thunderbird funciona?

Thunderbird usa la URL completa que se introduce. Un teléfono sólo ofrece un campo de servidor, por lo que sigue el mecanismo de descubrimiento de RFC 6764: solicita https://cal.example.com/.well-known/caldav y espera una redirección a la raíz DAV. Sin esa redirección, el teléfono recibe un 404 e indica que no puede verificar la cuenta. Añada location = /.well-known/caldav { return 301 https://$host/; } a nginx y confirme con curl -sI https://cal.example.com/.well-known/caldav que obtiene un 301 y una cabecera location.

¿Pueden dos personas compartir un calendario?

Sí. La forma más fiable es usar un inicio de sesión compartido. Cree una tercera cuenta con htpasswd, coloque el calendario compartido en ella y añádala como una segunda cuenta CalDAV en cada dispositivo. El archivo de permisos de Radicale puede conceder a un usuario identificado permisos de lectura y escritura sobre una colección situada bajo la ruta de otro usuario. Sin embargo, un cliente que sólo lea su propio conjunto de calendarios no la mostrará. Por tanto, esta opción es adecuada para Thunderbird y DAVx⁵, pero no para iOS.

¿Qué ocurre con mis eventos si el VPS deja de funcionar?

Con Radicale, el almacén es texto sin formato: hay un archivo .ics por evento bajo /var/lib/radicale/collections/collection-root/. Puede hacer una copia de seguridad con tar y leerla con less. Para restaurar, extraiga los archivos, ejecute chown -R radicale:radicale y arranque el servicio. Cada cliente sincronizado también conserva una copia local. Por tanto, un portátil que estuviera actualizado antes del fallo contiene una segunda copia completa del calendario.

¿Un servidor CalDAV sincroniza también mis contactos?

Los contactos usan CardDAV, un protocolo relacionado definido en RFC 6352 que almacena archivos vCard en lugar de eventos. Radicale, Baikal y Nextcloud pueden servirlos desde la misma cuenta y el mismo nombre de host. En Android, DAVx⁵ sincroniza calendarios y contactos desde una sola cuenta. En iOS, añada una segunda cuenta de tipo CardDAV con las mismas credenciales. Por eso la redirección /.well-known/carddav debe estar en la configuración de nginx junto a la de CalDAV.

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