SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor

Como configurar CalDAV no VPS com Radicale

Configure um servidor CalDAV no VPS com Radicale, TLS e descoberta para sincronizar calendários no telemóvel e portátil sem depender do Google.

O que você está construindo

Um calendário auto-hospedado é um servidor CalDAV num VPS sob o seu controlo, protegido por TLS, com um login por pessoa. O telemóvel que tem consigo e o portátil que está na sua secretária mostram os mesmos eventos, tal como o portátil do seu parceiro. Não há uma conta Google no meio.

Isto é diferente de uma página de reservas auto-hospedada. Uma página de reservas destina-se a desconhecidos: publica os seus horários livres e permite que alguém reserve um deles. Um servidor de calendários destina-se aos seus próprios dispositivos: armazena os eventos e mantém todos os clientes sincronizados. É comum utilizar ambos; nesse caso, a ferramenta de reservas consulta a disponibilidade no servidor CalDAV que vai configurar aqui.

A instalação é pequena. O Radicale é um pacote Python e requer cerca de dez linhas de configuração. O que determina se a configuração se mantém funcional durante o primeiro mês é o TLS, a descoberta, as coleções por utilizador e as cópias de segurança. A maior parte do texto abaixo é dedicada a esses pontos.

O que é CalDAV e por que é importante?

CalDAV é a sincronização de calendários através de HTTP. É definido na RFC 4791 como um conjunto de extensões ao WebDAV (web distributed authoring and versioning, um conjunto de métodos HTTP adicionais definido na RFC 4918). Um calendário é uma coleção que funciona como um diretório. Um evento é um ficheiro dentro dessa coleção, escrito no formato de texto iCalendar (RFC 5545), o mesmo formato usado nos anexos .ics do seu correio.

Os clientes usam HTTP normal com alguns métodos adicionais. PROPFIND pergunta o que existe e quais são as respetivas propriedades. REPORT pede um subconjunto filtrado, como todos os eventos num intervalo de datas. PUT escreve um evento e DELETE remove-o. Cada evento contém uma linha UID. Esse identificador permite que dois dispositivos confirmem que estão a consultar o mesmo evento, e não uma cópia.

A portabilidade é a principal vantagem e é a razão para usar CalDAV. iOS, macOS, Thunderbird, Evolution e Android através do DAVx⁵ suportam CalDAV. Os seus dados não ficam dependentes do servidor escolhido hoje. Mova os ficheiros para outro servidor CalDAV, aponte os clientes para o novo nome de anfitrião e nada mais muda.

O CardDAV funciona da mesma forma. É o mesmo conceito para contactos, definido na RFC 6352, e armazena ficheiros vCard em vez de eventos. Todos os servidores abaixo disponibilizam os dois protocolos na mesma conta. Assim, depois de o calendário funcionar, o livro de endereços resume-se a ativar uma opção.

Que servidor CalDAV deve utilizar?

Radicale é a opção mais pequena que funciona. É escrito em Python, não utiliza uma base de dados e armazena os dados numa pasta com ficheiros de texto simples. Este guia utiliza-o porque um calendário doméstico não precisa de mais e porque há muito pouco que possa falhar às 3 da manhã.

Baikal é a opção com um painel de administração web. Funciona com PHP e a biblioteca sabre/dav, mantém utilizadores e calendários em SQLite ou MySQL e permite adicionar uma pessoa no browser, em vez de utilizar a linha de comandos. Escolha-o quando as contas são criadas e removidas com frequência.

Nextcloud é adequado quando o calendário é apenas uma entre várias funcionalidades. Inclui calendário, contactos, ficheiros e uma aplicação móvel, mas requer PHP-FPM, uma base de dados e um executor de tarefas em segundo plano. Se isso parecer pesado para o que realmente precisa, as alternativas mais leves ao Nextcloud apresentam as opções, e a sincronização de ficheiros autoalojada cobre a outra metade das utilizações para as quais as pessoas instalam o Nextcloud.

DAViCal é a opção PostgreSQL com mais tempo de existência. Só vale a pena considerá-lo se já utilizar PostgreSQL e quiser manter os dados dos calendários nessa base de dados.

Instalar o Radicale no Ubuntu 24.04

O Radicale 3.5.10 era a versão atual em agosto de 2026. Instale-o no seu próprio ambiente 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

O ambiente virtual não é uma escolha de estilo. sudo pip install radicale no Python do sistema falha com error: externally-managed-environment, porque o Ubuntu marca o Python como pertencente ao apt para impedir que o pip substitua ficheiros instalados por pacotes.

Escreva /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 fica ligado ao loopback de propósito. O nginx termina o TLS e encaminha as ligações para essa porta, por isso o Radicale nunca fica diretamente exposto à Internet. O exemplo a montante de 0.0.0.0:5232 publica um serviço sem encriptação que aceita palavras-passe. Esse é o erro relevante neste caso.

Agora crie as contas. -5 seleciona crypt com SHA-512, que o Radicale lê com htpasswd_encryption = autodetect sem precisar de módulos adicionais:

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 cria o ficheiro e trunca o conteúdo existente. Use-o apenas para o primeiro utilizador. Executar htpasswd -5 -c novamente meses depois elimina todas as contas adicionadas após a primeira. O sintoma é uma pessoa conseguir sincronizar normalmente enquanto todas as outras recebem um pedido de palavra-passe que nunca termina. O Bcrypt também funciona e requer a instalação adicional radicale[bcrypt].

Crie /etc/systemd/system/radicale.service, adaptado da unidade presente na documentação do 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/

Um resultado normal é 401 Unauthorized com um cabeçalho WWW-Authenticate: o serviço está a escutar e a autenticação está ativa. Connection refused significa que o serviço nunca arrancou, e journalctl -u radicale -n 50 identifica a opção que rejeitou. ProtectSystem=strict monta o sistema de ficheiros como apenas de leitura para este serviço, por isso ReadWritePaths=/var/lib/radicale/ é a linha que permite guardar um evento. Remova essa linha e as leituras continuam a funcionar, mas todas as escritas falham.

TLS não é opcional, porque os clientes recusam texto sem encriptação

O CalDAV autentica-se com HTTP Basic, que envia user:password codificado em base64 em cada pedido. Base64 é uma codificação, não uma encriptação. Em HTTP simples, a palavra-passe fica exposta a todas as redes entre o telefone e o servidor, durante todo o dia e em cada sincronização.

Os clientes impõem este requisito. A documentação do Radicale indica que o macOS Calendar.app pode recusar silenciosamente o envio de credenciais por HTTP não seguro, e o iOS comporta-se da mesma forma. A conta parece estar configurada, mas nunca sincroniza e não há nenhum erro visível.

Aponte primeiro um registo A de cal.example.com para a VPS, porque a autoridade certificadora verifica esse registo. Em seguida, crie /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/; }
}

As quatro linhas de cabeçalho do proxy vêm da documentação do Radicale. Mantenha-as como estão.

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 apresenta syntax is ok e test is successful. Só faça o reload depois disso, porque um reload com um ficheiro inválido mantém a configuração antiga em execução e oculta o erro até ao próximo restart. O Certbot edita o ficheiro do site no local: instala o certificado, altera o bloco para a porta 443 e adiciona um redirecionamento da porta 80. O último curl pede a palavra-passe e deve devolver 200, que é a própria interface web do Radicale. Um 502 Bad Gateway significa que o nginx está em execução e que o Radicale não está a escutar na porta 5232.

Por que a adição da conta falha no telemóvel?

Por causa da descoberta. A RFC 6764 descreve como um cliente transforma um nome de host num URL de calendário. Procura um registo SRV _caldavs._tcp, depois solicita https://cal.example.com/.well-known/caldav e espera um redirecionamento para a raiz DAV. A partir daí, solicita current-user-principal, depois calendar-home-set desse principal, e só então encontra os seus calendários. O telemóvel fornece um único campo para o servidor, por isso todas as etapas têm de funcionar sem intervenção.

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

A resposta correta é HTTP/2 301 com um cabeçalho location: https://cal.example.com/. Um 404 nesse ponto faz com que o iOS indique que não consegue verificar as informações da conta, enquanto o Thunderbird funciona na mesma rede. O Thunderbird usa o URL completo introduzido por si, por isso nunca precisa do redirecionamento.

O destino do redirecionamento depende do servidor. O Radicale servido na raiz do site redireciona para /. O Baikal inclui regras de exemplo que redirecionam para /dav.php com o estado 308. O Nextcloud redireciona para /remote.php/dav/.

Crie os calendários e partilhe um deles com o seu parceiro

Muitos clientes não conseguem criar um calendário; apenas conseguem subscrever um. Abra https://cal.example.com/ num navegador, inicie sessão como you e crie o calendário nesse local. No disco, ele fica em /var/lib/radicale/collections/collection-root/you/, com um identificador gerado para o nome da pasta.

O backend de permissões predefinido do Radicale é owner_only: uma conta autenticada lê e escreve as próprias coleções em /USERNAME/, e nada mais. Para a maioria das famílias, esta é a configuração correta. A forma mais simples de partilhar um calendário é usar uma terceira conta. Crie household com htpasswd, crie o calendário partilhado nessa conta e adicione-o em cada dispositivo como uma segunda conta CalDAV. Funciona em todos os clientes, incluindo iOS, porque o calendário fica na home própria dessa conta.

Quando precisar de um controlo mais granular, mude para permissões baseadas em regras. Adicione isto a /etc/radicale/config:

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

Depois, /etc/radicale/rights, com base no exemplo da documentação do 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

As letras maiúsculas e minúsculas têm significados diferentes. R e W leem e escrevem coleções que não são calendários nem livros de endereços, que é o caso de uma pasta principal. r e w leem e escrevem os próprios calendários. Substitua esse identificador pelo nome real da pasta do seu calendário, obtido no caminho de armazenamento acima.

Há uma limitação importante: um cliente que apenas lê o conjunto de homes de calendários não apresenta um calendário localizado no caminho de outro utilizador, porque a descoberta nunca percorre esse caminho. O Thunderbird e o DAVx⁵ conseguem adicioná-lo através do URL completo. O iOS não consegue. Por isso, o padrão com uma conta partilhada é o que funciona sempre.

Configure os clientes, porque é aqui que os calendários self-hosted deixam de funcionar

iPhone e iPad. Abra Settings e selecione Calendar (em versões recentes do iOS, encontra-se em Apps), depois Calendar Accounts, Add Account, Other e Add CalDAV Account. O servidor é cal.example.com, seguido do nome de utilizador e da palavra-passe. Description é apenas uma etiqueta. Se não for possível guardar a conta, abra-a novamente: a vista avançada mostra Use SSL, a porta e o URL completo da conta. Colar o URL evita totalmente a descoberta automática.

Android. Não existe um cliente CalDAV integrado. Instale o DAVx⁵ a partir do F-Droid ou do Google Play e adicione uma conta utilizando o URL base https://cal.example.com/ e o seu nome de utilizador. Em seguida, selecione os calendários pretendidos. O DAVx⁵ escreve no fornecedor de calendários do Android, por isso os eventos aparecem na aplicação de calendário que já utiliza.

Thunderbird. Selecione New Calendar, On the Network e introduza o seu nome de utilizador e a localização https://cal.example.com/. O Thunderbird apresenta o que encontrou e pergunta quais os calendários que pretende adicionar.

macOS. Abra System Settings, Internet Accounts, Add Other Account e CalDAV. Defina Account Type como Manual e introduza o mesmo nome de utilizador, palavra-passe e endereço do servidor.

O CalDAV é um protocolo baseado em polling. A especificação não inclui push, por isso um evento adicionado no portátil chega ao telefone na sincronização seguinte, e não necessariamente no mesmo segundo. Defina em cada cliente um intervalo que seja aceitável e lembre-se de que um intervalo mais curto num telefone consome mais bateria.

Faça backup da store, que contém apenas ficheiros

No Radicale, o seu calendário é um diretório de ficheiros .ics, um ficheiro por evento, além de um pequeno ficheiro de propriedades por coleção. Qualquer ferramenta que copie um diretório faz o backup. Pode abrir um backup com less para confirmar que contém eventos reais. Esta é uma vantagem concreta em relação a um dump de base de dados que não pode ler.

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

Pare o serviço durante os poucos segundos necessários para criar o arquivo. Assim, nenhum cliente fica a meio de uma operação de escrita enquanto os ficheiros são lidos. Depois, copie o arquivo para fora do servidor. Um backup no mesmo VPS não sobrevive à falha que está a tentar evitar. A restauração é o processo inverso: extraia os ficheiros, sudo chown -R radicale:radicale /var/lib/radicale/collections e inicie o serviço. Cada cliente também mantém uma cópia local dos calendários. Assim, um portátil que não tenha sincronizado desde a falha constitui uma segunda cópia dos seus dados.

Quando Baikal ou Nextcloud é a melhor opção

O Baikal 0.12.1 foi lançado em 5 August 2026 e requer PHP 8.2 ou posterior. Extraia-o fora da raiz web e exponha apenas o diretório 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

Esses dois diretórios são os únicos nos quais o servidor web escreve, portanto mais nada precisa de permissões de escrita. Dentro do bloco de servidor do nginx, as partes específicas do Baikal são 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; }

Recarregue o nginx, abra o site num navegador e o assistente de configuração criará a conta de administrador e a base de dados SQLite. A configuração do cliente é idêntica à do Radicale, com https://cal.example.com/ como endereço do servidor, porque a regra well-known encaminha a descoberta para /dav.php.

O Nextcloud só compensa se também quiser ficheiros e uma aplicação para telemóvel com o mesmo início de sessão. A raiz DAV é /remote.php/dav/ e aplicam-se as mesmas regras de descoberta. Para qualquer uma destas opções, executar o serviço num contentor mantém as versões do PHP fora do host: Docker Compose numa VPS explica o ficheiro Compose e o reverse proxy à frente dele, e o que vale a pena alojar por conta própria em 2026 é um bom ponto para decidir até onde quer avançar.

Modos de falha e mensagens que verá

Cada sincronização devolve 401. O ficheiro de palavras-passe perdeu as contas durante uma segunda execução de htpasswd -c, ou o utilizador radicale não consegue lê-lo. Verifique com sudo -u radicale cat /etc/radicale/users; um erro de permissão nesse comando confirma o problema. A correção é usar o grupo radicale e o modo 640. Por predefinição, o Radicale também espera um segundo depois de cada início de sessão falhado. Por isso, um cliente com uma palavra-passe antiga parece lento em vez de ser rejeitado.

O nginx responde 405 a PROPFIND. O URL está a ser servido como ficheiro estático, por isso o método WebDAV nunca chega ao Radicale. Teste o endpoint diretamente:

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

Uma coleção DAV funcional responde 207 Multi-Status. Qualquer outro resultado significa que o pedido foi interrompido no servidor Web.

O telefone não consegue validar a conta, mas o browser funciona. Há duas causas comuns. Falta o redirecionamento well-known, que pode ser testado com o comando curl acima. Ou a cadeia de certificados está incompleta. Os browsers contornam este problema ao obter o certificado intermédio em falta, mas o iOS não o faz. Verifique a partir da shell:

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

Procure Verify return code: 0 (ok). Se falhar, a configuração do nginx aponta para cert.pem quando deveria apontar para fullchain.pem.

Eventos duplicados depois de uma importação. Cada evento contém um UID, que os clientes usam como identidade. Se importar o mesmo ficheiro duas vezes com uma ferramenta que gera identificadores novamente, obterá dois eventos que nunca serão unidos. Elimine as cópias adicionais num dispositivo e deixe a eliminação ser sincronizada.

Tudo deixa de funcionar depois de um reboot. O serviço foi iniciado manualmente. sudo systemctl is-enabled radicale mostra disabled, e sudo systemctl enable --now radicale corrige o problema de forma permanente.

FAQ

Preciso mesmo de TLS para um servidor CalDAV autoalojado?

Sim. O CalDAV autentica com HTTP Basic, por isso a palavra-passe é enviada codificada em base64 em cada pedido, e base64 pode ser revertido de forma trivial. Os clientes também impõem esta condição: o macOS Calendar.app pode recusar silenciosamente o envio de credenciais através de HTTP sem segurança, e o iOS comporta-se da mesma forma. Por isso, a conta parece ser guardada, mas nunca sincroniza. sudo certbot --nginx -d cal.example.com trata de todo o processo.

Porque é que o meu telefone falha ao adicionar a conta quando o Thunderbird funciona?

O Thunderbird usa o URL completo que introduziu. Um telefone disponibiliza um único campo de servidor, por isso segue a descoberta definida na RFC 6764: pede https://cal.example.com/.well-known/caldav e espera um redirecionamento para a raiz DAV. Sem esse redirecionamento, o telefone recebe um 404 e indica que não consegue verificar a conta. Adicione location = /.well-known/caldav { return 301 https://$host/; } ao nginx e confirme com curl -sI https://cal.example.com/.well-known/caldav que obtém um 301 e um cabeçalho location.

Duas pessoas podem partilhar um calendário?

Sim. A forma mais fiável é usar uma conta partilhada. Crie uma terceira conta com htpasswd, coloque o calendário partilhado nessa conta e adicione-a como uma segunda conta CalDAV em cada dispositivo. Em alternativa, o ficheiro de permissões do Radicale pode conceder a um utilizador identificado acesso de leitura e escrita a uma coleção no caminho de outro utilizador. No entanto, um cliente que leia apenas o conjunto de calendários da sua própria área inicial nunca a apresentará. Por isso, essa opção é adequada para Thunderbird e DAVx⁵, mas não para iOS.

O que acontece aos meus eventos se o VPS falhar?

No Radicale, o armazenamento é composto por texto simples: um ficheiro .ics por evento em /var/lib/radicale/collections/collection-root/. Pode fazer uma cópia de segurança com tar e lê-la com less. Para restaurar, extraia os ficheiros, execute chown -R radicale:radicale e inicie o serviço. Cada cliente sincronizado também mantém uma cópia local. Assim, um portátil que estivesse atualizado antes da falha contém uma segunda cópia completa do calendário.

Um servidor CalDAV também sincroniza os meus contactos?

Os contactos usam CardDAV, um protocolo relacionado definido na RFC 6352 que armazena ficheiros vCard em vez de eventos. Radicale, Baikal e Nextcloud disponibilizam-no através da mesma conta e do mesmo hostname. No Android, o DAVx⁵ sincroniza calendários e contactos a partir de uma única conta. No iOS, adicione uma segunda conta do tipo CardDAV com as mesmas credenciais. É por isso que o redirecionamento /.well-known/carddav deve estar na configuração do nginx, junto ao redirecionamento do CalDAV.

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