History of Radicale Cribsheet
| Version 4 | Current version | |
|---|---|---|
Radicale CalDAV/CardDAV Server - Key Points{toc} InstallingConfiguration `/etc/radicale/config`Key active settings: Critical systemd overrideDefault service restricts writes to `/var/lib/radicale/` — must add `/srv/radicale/` to allowed paths. Copy service file to webstack and add: <code>ReadWritePaths=/var/lib/radicale/collections /srv/radicale/ </code> Storage setup<code>mkdir -p /srv/radicale/collections chown -R radicale:radicale /srv/radicale chmod 755 /srv/radicale chmod 755 /srv/radicale/collections </code> **Critical — directories need 755 not 644!** Collection structure
<code># Create directory structure
mkdir -p /srv/radicale/collections/collection-root/lsces/calendar
mkdir -p /srv/radicale/collections/collection-root/lsces/contacts
# Props files — collection-root needs one too!
echo '{}' > /srv/radicale/collections/collection-root/.Radicale.props
cat > /srv/radicale/collections/collection-root/lsces/.Radicale.props << 'EOF'
{"D:displayname": "lsces"}
EOF
cat > /srv/radicale/collections/collection-root/lsces/calendar/.Radicale.props << 'EOF'
{"C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO", "D:displayname": "Personal Calendar", "tag": "VCALENDAR"}
EOF
cat > /srv/radicale/collections/collection-root/lsces/contacts/.Radicale.props << 'EOF'
{"D:displayname": "Personal Contacts", "tag": "VADDRESSBOOK"}
EOF
chown -R radicale:radicale /srv/radicale/collections
</code>
Create user<code>htpasswd -B -c /etc/radicale/users lsces </code> Enable and start<code>systemctl daemon-reload systemctl enable radicale systemctl start radicale systemctl status radicale </code> nginx reverse proxy
<code>location /dav/ {
proxy_pass http://localhost:5232/;
proxy_set_header X-Script-Name /dav;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass_header Authorization;
proxy_set_header Authorization $http_authorization;
}
</code>
**Both Authorization headers needed — nginx strips it without explicit passthrough!** Migrating from Nextcloud**Calendar export** — Nextcloud UI: Calendar → Edit → Export → saves `.ics` Radicale requires **one event per file** — split the export:
<code>import re, os
with open('personal.ics', 'r') as f:
content = f.read()
header = content[:content.index('BEGIN:VEVENT')]
footer = 'END:VCALENDARn'
events = re.findall('BEGIN:VEVENT.*?END:VEVENT', content, re.DOTALL)
os.makedirs('/srv/radicale/split', exist_ok=True)
for event in events:
uid = re.search('UID:(.*?)n', event).group(1).strip()
with open(f'/srv/radicale/split/{uid}.ics', 'w') as f:
f.write(header)
f.write(event + 'n')
f.write(footer)
print(f'Split {len(events)} events')
</code>
**Contacts export** — Nextcloud UI: Contacts → Settings → Export → saves `.vcf` Same split needed for contacts:
<code>import re, os
with open('contacts.vcf', 'r') as f:
content = f.read()
contacts = re.findall('BEGIN:VCARD.*?END:VCARD', content, re.DOTALL)
os.makedirs('/srv/radicale/split_contacts', exist_ok=True)
for contact in contacts:
uid = re.search('UID:(.*?)n', contact)
uid = uid.group(1).strip() if uid else f'contact_{contacts.index(contact)}'
with open(f'/srv/radicale/split_contacts/{uid}.vcf', 'w') as f:
f.write(contact + 'n')
print(f'Split {len(contacts)} contacts')
</code>
**Import via curl:**
<code># Calendar events
for f in /srv/radicale/split/*.ics; do
uid=$(basename "$f" .ics)
curl -s -u lsces:password
-X PUT
-H "Content-Type: text/calendar"
--data-binary @"$f"
"https://rdm1.uk/dav/lsces/calendar/${uid}.ics"
echo "$uid"
done
# Contacts
for f in /srv/radicale/split_contacts/*.vcf; do
uid=$(basename "$f" .vcf)
curl -s -u lsces:password
-X PUT
-H "Content-Type: text/vcard"
--data-binary @"$f"
"https://rdm1.uk/dav/lsces/contacts/${uid}.vcf"
echo "$uid"
done
</code>
Client configuration**Thunderbird** — Add new calendar/contacts: - Network → CalDAV/CardDAV - URL: `https://yourdomain/dav/username/calendar/` **DAVx⁵ (Android)** — Add account: - Base URL: `https://yourdomain/dav/username/` - Auto-discovers both calendar and contacts | Radicale CalDAV/CardDAV Server - Key PointsInstallingzypper install radicale # Creates radicale:radicale system user automatically Configuration `/etc/radicale/config`Key active settings: [server] hosts = localhost:5232 [auth] type = htpasswd htpasswd_filename = /etc/radicale/users htpasswd_encryption = autodetect [storage] filesystem_folder = /srv/radicale/collections Critical systemd overrideDefault service restricts writes to `/var/lib/radicale/` — must add `/srv/radicale/` to allowed paths. Copy service file to webstack and add: ReadWritePaths=/var/lib/radicale/collections /srv/radicale/ Storage setupmkdir -p /srv/radicale/collections chown -R radicale:radicale /srv/radicale chmod 755 /srv/radicale chmod 755 /srv/radicale/collections **Critical — directories need 755 not 644!** Collection structure
# Create directory structure
mkdir -p /srv/radicale/collections/collection-root/lsces/calendar
mkdir -p /srv/radicale/collections/collection-root/lsces/contacts
# Props files — collection-root needs one too!
echo '{}' > /srv/radicale/collections/collection-root/.Radicale.props
cat > /srv/radicale/collections/collection-root/lsces/.Radicale.props << 'EOF'
{"D:displayname": "lsces"}
EOF
cat > /srv/radicale/collections/collection-root/lsces/calendar/.Radicale.props << 'EOF'
{"C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO", "D:displayname": "Personal Calendar", "tag": "VCALENDAR"}
EOF
cat > /srv/radicale/collections/collection-root/lsces/contacts/.Radicale.props << 'EOF'
{"D:displayname": "Personal Contacts", "tag": "VADDRESSBOOK"}
EOF
chown -R radicale:radicale /srv/radicale/collections
Create userhtpasswd -B -c /etc/radicale/users lsces Enable and startsystemctl daemon-reload systemctl enable radicale systemctl start radicale systemctl status radicale nginx reverse proxy
location /dav/ {
proxy_pass http://localhost:5232/;
proxy_set_header X-Script-Name /dav;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass_header Authorization;
proxy_set_header Authorization $http_authorization;
}
Both Authorization headers needed — nginx strips it without explicit passthrough! Migrating from NextcloudCalendar export — Nextcloud UI:
import re, os
with open('personal.ics', 'r') as f:
content = f.read()
header = content[:content.index('BEGIN:VEVENT')]
footer = 'END:VCALENDARn'
events = re.findall('BEGIN:VEVENT.*?END:VEVENT', content, re.DOTALL)
os.makedirs('/srv/radicale/split', exist_ok=True)
for event in events:
uid = re.search('UID:(.*?)n', event).group(1).strip()
with open(f'/srv/radicale/split/{uid}.ics', 'w') as f:
f.write(header)
f.write(event + 'n')
f.write(footer)
print(f'Split {len(events)} events')
**Contacts export** — Nextcloud UI: Contacts → Settings → Export → saves `.vcf` Same split needed for contacts:
import re, os
with open('contacts.vcf', 'r') as f:
content = f.read()
contacts = re.findall('BEGIN:VCARD.*?END:VCARD', content, re.DOTALL)
os.makedirs('/srv/radicale/split_contacts', exist_ok=True)
for contact in contacts:
uid = re.search('UID:(.*?)n', contact)
uid = uid.group(1).strip() if uid else f'contact_{contacts.index(contact)}'
with open(f'/srv/radicale/split_contacts/{uid}.vcf', 'w') as f:
f.write(contact + 'n')
print(f'Split {len(contacts)} contacts')
**Import via curl:**
# Calendar events
for f in /srv/radicale/split/*.ics; do
uid=$(basename "$f" .ics)
curl -s -u lsces:password
-X PUT
-H "Content-Type: text/calendar"
--data-binary @"$f"
"https://rdm1.uk/dav/lsces/calendar/${uid}.ics"
echo "$uid"
done
# Contacts
for f in /srv/radicale/split_contacts/*.vcf; do
uid=$(basename "$f" .vcf)
curl -s -u lsces:password
-X PUT
-H "Content-Type: text/vcard"
--data-binary @"$f"
"https://rdm1.uk/dav/lsces/contacts/${uid}.vcf"
echo "$uid"
done
Client configuration
Developed with Claude AI assistance - Anthropic - April 2026 |
