Why Self-Hosted Calendar and Contact Sync Still Makes Sense
Cloud calendar and contact services from Google, Apple, and Microsoft are convenient until they are not. Outages, privacy policy changes, storage limits, and account lockouts have pushed a growing number of users toward self-hosted alternatives that they actually control. Radicale is a lightweight, open-source CalDAV and CardDAV server written in Python that runs on virtually any Linux system, handles multiple users, and requires almost no ongoing maintenance once configured.
Unlike heavier groupware stacks that bundle email, file sharing, and project management into one bloated package, Radicale does exactly two things: it stores calendars and contacts, and it serves them over standard protocols. Every modern calendar app – Thunderbird, Apple Calendar, GNOME Calendar, and most Android clients – speaks CalDAV and CardDAV natively. That means once Radicale is running, syncing works without installing any special software on client devices.

Installing Radicale on a Linux Server
Radicale runs on Python 3.3 or newer, so most current Linux distributions have everything needed already installed. On Debian or Ubuntu, start by installing pip and then Radicale itself. Open a terminal and run sudo apt update && sudo apt install python3-pip -y, followed by sudo pip3 install radicale. If you prefer to keep it isolated, install inside a virtual environment using python3 -m venv radicale-env && source radicale-env/bin/activate && pip install radicale. Either approach works for a home server or a small VPS.
Once installed, Radicale needs a dedicated system user so it does not run as root. Create one with sudo useradd –system –home /var/lib/radicale –create-home radicale. This user will own the data directory where all calendar and contact collections are stored. Running services under a dedicated low-privilege account is a basic security practice that limits damage if anything goes wrong.
Configuring Radicale for Authentication and Storage
Radicale reads its settings from a config file, typically placed at /etc/radicale/config. Create that directory first with sudo mkdir -p /etc/radicale, then open a new file with your editor of choice. The configuration uses INI-style sections. The most important sections to define immediately are [auth], [storage], and [server].
For authentication, set type = htpasswd under [auth] and point it at a password file using htpasswd_filename = /etc/radicale/users. Set htpasswd_encryption = bcrypt for proper password hashing. To create the password file and add a first user, run sudo htpasswd -c -B /etc/radicale/users youruser. Adding subsequent users drops the -c flag so the file is not overwritten: sudo htpasswd -B /etc/radicale/users seconduser.
Under [storage], set the filesystem path with filesystem_folder = /var/lib/radicale/collections. Radicale will create subdirectories for each user automatically on first login. Under [server], bind to localhost only for now: hosts = 127.0.0.1:5232. Exposing port 5232 directly to the internet without TLS is a bad idea, and the next step – putting Radicale behind a reverse proxy – addresses that.
The complete minimal config file looks like this:
- [auth] – type = htpasswd, htpasswd_filename = /etc/radicale/users, htpasswd_encryption = bcrypt
- [storage] – filesystem_folder = /var/lib/radicale/collections
- [server] – hosts = 127.0.0.1:5232
Set ownership of the config file and data directory to the radicale system user: sudo chown -R radicale:radicale /etc/radicale /var/lib/radicale. File permissions matter here. The users password file should be readable only by the radicale user, so run sudo chmod 600 /etc/radicale/users to lock it down.

Running Radicale as a systemd Service
To keep Radicale running across reboots, create a systemd unit file at /etc/systemd/system/radicale.service. The unit file should define the service type as simple, set the user and group to radicale, and call the Radicale binary with the config path as an argument. A working unit file looks like this: under [Service], add User=radicale, Group=radicale, and ExecStart=/usr/local/bin/radicale –config /etc/radicale/config. Set Restart=on-failure so systemd restarts the process automatically if it crashes.
After saving the file, reload systemd and enable the service: sudo systemctl daemon-reload && sudo systemctl enable –now radicale. Check that it started cleanly with sudo systemctl status radicale. The output should show active (running) with no errors. Test local connectivity with curl -u youruser:yourpassword http://127.0.0.1:5232/, which should return a basic HTML response from Radicale confirming the server is alive.
Putting Radicale Behind Nginx with HTTPS
Serving Radicale over plain HTTP is only acceptable on a local network you fully control. For any internet-facing setup, reverse proxying through Nginx with a valid TLS certificate is the correct approach. If Nginx is not already installed, add it with sudo apt install nginx -y. For TLS certificates, Certbot with Let’s Encrypt handles this cleanly on public servers: sudo apt install certbot python3-certbot-nginx -y, then run sudo certbot –nginx -d cal.yourdomain.com.
Create an Nginx server block for Radicale. The key directives inside the location / block are proxy_pass http://127.0.0.1:5232; and a set of proxy headers that preserve the original request information. Add proxy_set_header X-Script-Name “”;, proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;, and proxy_set_header Host $host;. Also include proxy_buffering off; to avoid issues with large calendar data payloads being buffered before reaching clients.
If you are already running a reverse proxy on your server for other self-hosted services – such as a self-hosted tunnel and auth gateway like Pangolin – you can route Radicale through the same infrastructure rather than managing a separate Nginx instance. Reload Nginx after saving your config: sudo systemctl reload nginx. Then update the [server] section of your Radicale config to confirm it is only binding to localhost, since Nginx is now the public-facing entry point.

Connecting Clients and Creating Collections
With the server running and accessible over HTTPS, connecting a client is straightforward. In Thunderbird with the Calendar add-on, add a new network calendar and enter the URL as https://cal.yourdomain.com/youruser/. Radicale will automatically discover any calendar collections stored under that user path. On iOS and macOS, go to Settings, then Calendar or Contacts, add a new CalDAV or CardDAV account, and enter the server URL along with the username and password you created. Android users can use DAVx5, a free app available on F-Droid that handles both CalDAV and CardDAV accounts in one place.
Creating your first calendar collection can be done either through a compatible client – which will create one automatically on first sync – or manually by making a directory inside /var/lib/radicale/collections/collection-root/youruser/ with the appropriate properties file. Most clients handle collection creation without manual intervention. After the first successful sync, Radicale logs connection activity to the system journal, viewable with journalctl -u radicale -f, which is useful for confirming that client connections are authenticating correctly and that no permission errors are occurring on the storage path.
Frequently Asked Questions
Is Radicale suitable for multiple users?
Yes. Radicale supports multiple users through an htpasswd file, and each user gets their own isolated collection directory for calendars and contacts.
Can I access Radicale from my iPhone or Android phone?
Yes. iOS and macOS support CalDAV and CardDAV natively in Settings. Android users can connect using the free DAVx5 app available on F-Droid.





