Host your own contacts and calendars and share them across devices

I’m trying to learn ways to minimize my reliance upon large companies for handling my day-to-day personal data. So I figured calendar and contacts should be on my list of things to self-host. This post is about how I migrated all my Google calendars and phone contacts to my own server without losing any features I was using. I’m doing this mostly for fun.

My self-hosted calendar in the web client
My self-hosted calendar on the phone

Installing Radicale CalDAV/CardDAV server behind an Apache Reverse Proxy

First step is to choose a CalDAV/CardDAV server. These are standard file formats that hold calendar and contact info, and there are lots of choices. I found Radicale, which I liked because it was small, simple, open source, and written in Python, my favorite programming language! In its simplest form, it just runs on a port of your server, and you can test it out like that.

I wanted the extra security of running it behind the more robust web server I already had running (Apache) with authentication, so I had to set up a Reverse Proxy, where Apache passes traffic to the Radicale instance and back. The instructions work but are specific to nginx, so I had to figure out what to do for Apache. Here’s what I got:

ProxyPass /radicale/
<Location /radicale/>
    ProxyPassReverse /radicale/
    RewriteRule ^/radicale/(.*)$$1 [QSA,L,E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    RequestHeader set X-Remote-User %{REMOTE_USER}s
    RequestHeader set X-Script-Name /radicale
    AuthType Basic
    AuthName "Secret Self-Hosted Calendar"
    require valid-user
    AuthUserFile "/path/to/my/htpasswd"

This authenticates users that hit the /radicale/ folder and then sends everything to the Radicale server chilling on port 8822. I put two users in my htpasswd file, one for me and the other to be shared across the family.

I also have an older server that’s still running upstart so I had to come up with the upstart version of the startup script for Radicale:

description "Radicale CalDAV and CardDAV server"
setuid radicaleuser
start on net-device-up
exec /usr/bin/env python3 -m radicale --config /etc/radicale/config

With that all in place, I could get to the server’s web interface, log in with the htpasswd info, and add some empty calendars and addressbooks.

Radicale web interface working

I created a one calendar in my personal folder and another in the family account and then symbolically linked the family one into my folder in the Radicale storage area so I could share it easily.

Importing, Organizing, and Merging Contacts

Now to populate the server! I had several overlapping contact lists on various devices:

  • Some old phone numbers on my SIM card on my phone. I decided to ignore most of these because the majority of them are duplicated elsewhere.
  • More recent phone numbers and a few email addresses in my Google Contacts, gathered from my phone. I exported these all with the Export function in vCard format to my PC.
  • A bunch of email addresses on my Thunderbird email client on my laptop. If you use a web-based email, just export your contacts from there to a file.

I cleaned these all up and merged them by first connecting CardBook to my contact URL shown on the Radicale web interface and then right-clicking the address book and selecting “Import contacts from a file.” Import however many files you exported. Then I ran the CardBook merge tool to combine all duplicates and do other cleanups. It was pretty satisfying.

I had a few configuration things wrong and just ran a tail -f  on the Radicale log. When syncing wasn’t working, I saw exceptions there when blank FN (Family Name) entries weren’t allowed by Radicale and any contact that was missing FN was crashing the sync.

Cardbook screenshot with contacts listed
Cardbook with contacts loaded up show in Thunderbird

Transferring calendars to Radicale

Moving my calendars was straightforward using the Lightning Thunderbird plugin. I just exported them from where they were (Google Calendar) and imported them in Lightning into my new calendar. To get access to the family account as well as my personal one from Thunderbird, I had to activate the setting in Thunderbird advanced config so it would ask me for multiple credentials.

Syncing to Android phones and iPhones

Obviously the calendars and contacts need to be shared with the phone. For Android, I installed the open-source DAVDroid which you can get for a few bucks on the Play Store or for free on F-Droid. Just type in the URL from the Radicale web interface and you’ll be off and running with Calendar and Contact Sync. The calendar shows up in any calendar app (including Google Calendar) and the contacts show up in any contacts app. I then filtered out and/or deleted the contacts and calendars stored on other people’s servers.

For the my wife’s iPhone, I just synced the family calendar. It was as easy as going to Settings->Accounts and adding a new CalDAV account pointing to the URL from the Radicale web interface and typing in the htpasswd credentials. Then the calendar showed up in any/all calendar apps.

Web calendar client

I wasn’t sure if I was ready to just use Thunderbird Lightning as my calendar client because I’m so used to a web interface. Quick searching led me to AgenDAV, an open-source CalDAV web client. One more quick Apache config and we were off and running. (You could also just put this in its own subdomain).

Alias /agendav  /opt/agendav-2.0.0/web/public
<Location /agendav>
           Require all granted
           RewriteEngine On
           RewriteCond %{REQUEST_FILENAME} !-f 
           RewriteRule ^ index.php [QSA,L]

Looks pretty nice! (Screenshot at top of page)

You can’t directly add an external link to an external calendar (like a public rec soccer league one) so in those scenarios just import it into the server first and it will show up here.


All in, this was less than a day of work. Not bad for me since I had been really wanting to do some cleanup on my contacts for a long time anyway. Granted, doing all this means I have to maintain it and try to secure it myself (something I’ll surely suffer the consequences of someday). At least I saved backups of everything in the process. Normal people might want to just consider a more in-the-box solution such as Nextcloud but I personally just enjoy learning and doing these kinds of things.

Discuss on Hacker News

7 thoughts on “Host your own contacts and calendars and share them across devices”

  1. This looks quite good. I have always wanted to do something like this, but gave up pretty quickly. Have you hosted this on the Internet, or do you run this on your home, and have your phones sync up only when you are home?

  2. GREAT! Will install it tomorrow morning for my confused calendar life… ;O) The testinstall was successfull.

    The ruebenmaster

  3. Nice work, thank you for documenting this, this has been on my todo list for a while. Have you tried Calendar on OS X (High Sierra)? I am having trouble syncing with both Radicale and Baikal, although other clients including iOS work as expected. Am wondering if this is my config or a quirk of the Apple apps… I can’t get Contacts on OS X to login either (although it works on iOS).

    Also, any idea how to make that Apache config work for a subdomain? Thanks

    1. Hey thanks a lot.

      I did get it synced to an iPhone but I don’t think it was on OS X (High Sierra) so I may not have gotten any farther than you. See anything useful in logs?

      For an apache subdomain, I suspect you’d just have to put the config entries above in the subdomain section with a:


      Is that not working? I wonder what else you have to tweak.

      Good luck mate.

Leave a Reply

Your email address will not be published. Required fields are marked *