Radicale: Lightweight CalDAV/CardDAV Server
Simple, resource-efficient calendar and contacts server. Perfect for small homelabs.
In the modern digital landscape, our calendars and contacts are often stored in the cloud by major tech companies. While convenient, this means surrendering control and privacy of your personal scheduling data. Radicale offers an elegant solution: a lightweight, Python-based CalDAV and CardDAV server that you can run on minimal hardware while maintaining complete control over your data.
This comprehensive guide will walk you through deploying Radicale in your homelab, from basic setup to advanced configurations with authentication, reverse proxies, and client configurations across all major platforms.
What is Radicale?
Radicale is an open-source CalDAV (calendars) and CardDAV (contacts) server designed with simplicity and minimalism in mind. Unlike heavyweight solutions, Radicale is incredibly lightweight, requiring minimal system resources while providing full standards compliance.
Why Choose Radicale?
1. Minimal Resource Footprint
Radicale is written in Python and designed to be lean. It runs smoothly on:
- Raspberry Pi (even older models)
- Docker containers with 64MB RAM limit
- Shared hosting environments
- Alongside other homelab services
2. Simple Configuration
No complex database setup required. Radicale stores data in simple filesystem structures, making backups and migration straightforward. The configuration file is intuitive and well-documented.
3. Standards Compliant
Full support for CalDAV and CardDAV standards means compatibility with virtually every calendar and contacts application:
- iOS and macOS (native support)
- Android (with DAVx5)
- Thunderbird
- Evolution
- Outlook (with plugins)
4. Security First
Built-in authentication options including:
- HTTP Basic/Digest authentication
- htpasswd file support
- LDAP integration
- Custom authentication backends
Radicale vs. Alternatives
| Feature | Radicale | Baikal | Nextcloud |
|---|---|---|---|
| RAM Usage | 50-100MB | 200-300MB | 512MB+ |
| Setup Complexity | Very Simple | Simple | Complex |
| Web Interface | Minimal | Full Admin Panel | Full Suite |
| Database Required | No | SQLite/MySQL | MySQL/PostgreSQL |
| Best For | Minimalists | Balance | Full Features |
Architecture Overview
Our Radicale deployment will include:
Internet
|
v
Reverse Proxy (Traefik/Nginx)
|
v
Radicale Container
|
v
Volume Storage (Calendar/Contact Data) This setup provides:
- HTTPS encryption via reverse proxy
- HTTP Basic authentication
- Persistent data storage
- Easy backup and restore
Prerequisites
Before starting, ensure you have:
- A Linux server or VM (1 CPU, 512MB RAM minimum)
- Docker and Docker Compose installed
- Basic understanding of terminal commands
- A domain name with DNS access (for SSL)
- Port 80/443 access for reverse proxy
Deployment: Getting Radicale Running
Let’s deploy Radicale with Docker Compose, which provides the most flexible and maintainable setup.
Step 1: Create Project Directory
# Create project directory
mkdir -p ~/radicale && cd ~/radicale
# Create directories for data and configuration
mkdir -p data config Step 2: Create Configuration File
Create a configuration file at config/config:
[server]
# Bind to all interfaces (Docker networking)
hosts = 0.0.0.0:5232
# Maximum number of parallel connections
max_connections = 20
# Maximum size for request body (10MB)
max_content_length = 10000000
# Timeout for client connections (seconds)
timeout = 30
[encoding]
# Encoding for requests and responses
request = utf-8
stock = utf-8
[auth]
# Authentication type: none, htpasswd, remote_user, http_x_remote_user
type = htpasswd
# Path to htpasswd file
htpasswd_filename = /config/users
htpasswd_encryption = bcrypt
[rights]
# Rights management: none, owner_only, owner_write, from_file
type = owner_only
[storage]
# Storage backend
type = multifs
# Filesystem folder for collections
filesystem_folder = /data/collections
[web]
# Web interface: none or internal
type = internal
[logging]
# Logging level: debug, info, warning, error, critical
level = info
# Enable full environment on crash
full_environment = false Step 3: Create User Password File
# Install apache2-utils for htpasswd command
sudo apt update && sudo apt install apache2-utils -y
# Create htpasswd file with your first user
# Replace 'username' with your desired username
htpasswd -B -c config/users username
# Add additional users (without -c flag)
htpasswd -B config/users anotheruser Step 4: Create Docker Compose Configuration
Create docker-compose.yml:
version: '3.8'
services:
radicale:
image: tomsquest/docker-radicale:latest
container_name: radicale
restart: unless-stopped
ports:
- "5232:5232"
volumes:
- ./data:/data
- ./config:/config:ro
environment:
- TZ=America/New_York
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5232"]
interval: 30s
timeout: 10s
retries: 3
networks:
- radicale-net
networks:
radicale-net:
driver: bridge Step 5: Launch Radicale
# Start the service
docker-compose up -d
# Check logs
docker-compose logs -f
# Verify it's running
curl http://localhost:5232 You should see a basic web interface at http://YOUR-SERVER-IP:5232
Setting Up Reverse Proxy with SSL
For production use, you’ll want HTTPS encryption. Let’s configure both Traefik and Nginx options.
Option 1: Traefik Reverse Proxy
If you’re using Traefik, update your docker-compose.yml:
version: '3.8'
services:
radicale:
image: tomsquest/docker-radicale:latest
container_name: radicale
restart: unless-stopped
volumes:
- ./data:/data
- ./config:/config:ro
environment:
- TZ=America/New_York
labels:
- "traefik.enable=true"
- "traefik.http.routers.radicale.rule=Host(`cal.yourdomain.com`)"
- "traefik.http.routers.radicale.entrypoints=websecure"
- "traefik.http.routers.radicale.tls.certresolver=letsencrypt"
- "traefik.http.services.radicale.loadbalancer.server.port=5232"
# Middleware for security headers
- "traefik.http.routers.radicale.middlewares=radicale-headers"
- "traefik.http.middlewares.radicale-headers.headers.sslredirect=true"
- "traefik.http.middlewares.radicale-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.radicale-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.radicale-headers.headers.stsPreload=true"
networks:
- traefik-net
networks:
traefik-net:
external: true Option 2: Nginx Reverse Proxy
Create an Nginx configuration file nginx/radicale.conf:
upstream radicale {
server radicale:5232;
}
server {
listen 80;
server_name cal.yourdomain.com;
# Redirect all HTTP to HTTPS
return 301 https://cal.yourdomain.com$request_uri;
}
server {
listen 443 ssl http2;
server_name cal.yourdomain.com;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
# Logging
access_log /var/log/nginx/radicale_access.log;
error_log /var/log/nginx/radicale_error.log;
location / {
proxy_pass http://radicale;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Script-Name /;
# Disable buffering for CalDAV/CardDAV
proxy_buffering off;
proxy_request_buffering off;
# Timeouts
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
}
} Add Nginx service to docker-compose.yml:
nginx:
image: nginx:alpine
container_name: radicale-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/radicale.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- radicale
networks:
- radicale-net Obtaining SSL Certificates
# Using Certbot for Let's Encrypt
sudo apt install certbot -y
# Generate certificate
sudo certbot certonly --standalone -d cal.yourdomain.com --email [email protected] --agree-tos
# Create SSL directory and copy certificates
mkdir -p ssl
sudo cp /etc/letsencrypt/live/cal.yourdomain.com/fullchain.pem ssl/
sudo cp /etc/letsencrypt/live/cal.yourdomain.com/privkey.pem ssl/
sudo chown $USER:$USER ssl/*.pem Client Configuration
Now let’s connect various devices and applications to your Radicale server.
iOS and macOS (Native Support)
Calendar Setup:
- Open Settings (iOS) or System Preferences (macOS)
- Navigate to Calendar > Accounts > Add Account
- Select “Other” > “Add CalDAV Account”
- Enter details:
- Server:
cal.yourdomain.com - Username:
your-username - Password:
your-password - Description:
Radicale Calendar
- Server:
- Advanced Options:
- Use SSL: Yes
- Port: 443
- Tap “Next” and wait for verification
Contacts Setup:
- Settings > Contacts > Accounts > Add Account
- Select “Other” > “Add CardDAV Account”
- Enter the same server details
- Description:
Radicale Contacts
Alternative Method (More Reliable):
Instead of using just the domain, use the full path:
- CalDAV URL:
https://cal.yourdomain.com/username/calendar-name/ - CardDAV URL:
https://cal.yourdomain.com/username/contacts/
Android with DAVx5
Installation:
- Install DAVx5 from F-Droid or Google Play Store
- Open DAVx5 and tap the ”+” button
- Select “Login with URL and user name”
Configuration:
- Base URL:
https://cal.yourdomain.com - User name:
your-username - Password:
your-password
- Tap “Login”
- DAVx5 will discover your calendars and address books
- Select which calendars and contacts to sync
- Grant necessary Android permissions
Syncing:
DAVx5 creates a new account on your Android device. You can now:
- Use any calendar app (Google Calendar, Simple Calendar, etc.)
- Use any contacts app (Google Contacts, Simple Contacts, etc.)
- Data syncs automatically in the background
Thunderbird (Linux/Windows/macOS)
Calendar Setup:
- Install Thunderbird and the Lightning add-on (built-in on newer versions)
- Right-click in the calendar list > “New Calendar”
- Select “On the Network”
- Choose “CalDAV”
- Location:
https://cal.yourdomain.com/username/calendar-name/ - Enter username and password when prompted
Contacts Setup:
- Tools > Address Book
- File > New > Remote Address Book
- Enter CardDAV details:
- URL:
https://cal.yourdomain.com/username/contacts/ - Name:
Radicale Contacts
- URL:
- Enter credentials
GNOME Evolution (Linux)
- Edit > Accounts > Add Account
- Select “CalDAV” for calendars or “CardDAV” for contacts
- Enter server details:
- Server:
cal.yourdomain.com - Username:
your-username - Use secure connection: Yes
- Server:
Windows Outlook
Outlook doesn’t natively support CalDAV/CardDAV, but you can use plugins:
CalDAV Synchronizer (Free):
- Download from GitHub: CalDavSynchronizer
- Install and restart Outlook
- CalDAV Synchronizer > Synchronization Profiles > Add
- Choose “Generic CalDAV/CardDAV”
- Configuration:
- CalDAV URL:
https://cal.yourdomain.com/username/calendar-name/ - Username and password
- Synchronization interval: 15 minutes
- CalDAV URL:
Advanced Configuration
LDAP Authentication
For centralized user management, integrate with LDAP:
Update config/config:
[auth]
type = ldap
# LDAP server configuration
ldap_uri = ldap://ldap.yourdomain.com:389
ldap_base = ou=users,dc=yourdomain,dc=com
ldap_reader_dn = cn=admin,dc=yourdomain,dc=com
ldap_secret = your-ldap-password
ldap_filter = (uid=%(login)s) Rights Management
Create fine-grained access controls with a rights file.
Update config/config:
[rights]
type = from_file
file = /config/rights Create config/rights:
# Everyone can read the root collection
[root]
user: .+
collection:
permissions: R
# Authenticated users can read and write their own collections
[owner]
user: .+
collection: {user}/.*
permissions: RW
# Admin has full access
[admin]
user: admin
collection: .*
permissions: RW
# Shared calendar - multiple users can write
[shared]
user: (alice|bob|charlie)
collection: shared/team-calendar
permissions: RW Storage Backends
Radicale supports multiple storage backends:
Default (multifs): Best for most use cases
[storage]
type = multifs
filesystem_folder = /data/collections Single Filesystem: All data in one folder
[storage]
type = multifs
filesystem_folder = /data/collections Custom Hooks: Run scripts on events
[storage]
hook = /scripts/backup-on-change.sh Logging and Debugging
For troubleshooting, enable debug logging:
[logging]
level = debug View logs in real-time:
docker-compose logs -f radicale Backup and Restore
Manual Backup
The beauty of Radicale’s filesystem storage is backup simplicity:
# Create backup script
cat > ~/radicale-backup.sh <<'EOF'
#!/bin/bash
BACKUP_DIR="/mnt/backups/radicale"
DATE=$(date +%Y%m%d_%H%M%S)
# Stop container for consistent backup
docker-compose -f ~/radicale/docker-compose.yml stop radicale
# Backup data directory
tar -czf ${BACKUP_DIR}/radicale-data-${DATE}.tar.gz -C ~/radicale data/
# Backup configuration
tar -czf ${BACKUP_DIR}/radicale-config-${DATE}.tar.gz -C ~/radicale config/
# Restart container
docker-compose -f ~/radicale/docker-compose.yml start radicale
# Remove backups older than 30 days
find ${BACKUP_DIR} -name "radicale-*.tar.gz" -mtime +30 -delete
echo "Backup completed: ${DATE}"
EOF
chmod +x ~/radicale-backup.sh
# Schedule daily backups
crontab -e
# Add: 0 2 * * * /home/user/radicale-backup.sh Automated Backup with Restic
For efficient, encrypted backups to cloud storage:
# Initialize Restic repository
restic init --repo /mnt/backups/radicale-restic
# Backup script with Restic
cat > ~/radicale-backup-restic.sh <<'EOF'
#!/bin/bash
export RESTIC_REPOSITORY="/mnt/backups/radicale-restic"
export RESTIC_PASSWORD="your-secure-password"
# Backup Radicale data
restic backup ~/radicale/data ~/radicale/config --tag radicale --exclude-file ~/radicale/.backup-exclude
# Keep last 7 daily, 4 weekly, 12 monthly backups
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
echo "Restic backup completed"
EOF
chmod +x ~/radicale-backup-restic.sh Restore from Backup
# Stop Radicale
docker-compose stop radicale
# Restore data
tar -xzf /mnt/backups/radicale/radicale-data-YYYYMMDD_HHMMSS.tar.gz -C ~/radicale/
# Restore config
tar -xzf /mnt/backups/radicale/radicale-config-YYYYMMDD_HHMMSS.tar.gz -C ~/radicale/
# Start Radicale
docker-compose start radicale Performance Optimization
Tuning for Large Datasets
If you have many calendars/contacts, optimize performance:
[server]
# Increase max connections
max_connections = 50
# Increase timeout for slow clients
timeout = 60
[storage]
# Enable filesystem caching
filesystem_fsync = false Monitoring Resource Usage
# Check container resource usage
docker stats radicale
# View detailed logs
docker logs radicale --tail 100 -f
# Check data directory size
du -sh ~/radicale/data Troubleshooting
Issue: Cannot Connect from Clients
Check server is accessible:
# Test locally
curl http://localhost:5232
# Test from another machine
curl http://YOUR-SERVER-IP:5232
# Check Docker logs
docker-compose logs radicale Verify authentication:
# Test with curl
curl -u username:password https://cal.yourdomain.com/
# Should return Radicale web interface HTML Issue: Sync Conflicts
CalDAV handles conflicts automatically, but if you experience issues:
# Check for corrupt files
find ~/radicale/data -name "*.ics" -o -name "*.vcf" | while read file; do
if ! iconv -f UTF-8 -t UTF-8 "$file" > /dev/null 2>&1; then
echo "Corrupt file: $file"
fi
done Issue: Permission Errors
# Fix ownership
sudo chown -R 2999:2999 ~/radicale/data
# Fix permissions
chmod 750 ~/radicale/data
chmod 640 ~/radicale/config/users Issue: SSL Certificate Errors
# Verify certificate
openssl s_client -connect cal.yourdomain.com:443 -servername cal.yourdomain.com
# Check certificate expiry
openssl s_client -connect cal.yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates Issue: High Memory Usage
# Set memory limit in docker-compose.yml
services:
radicale:
# ... other config
deploy:
resources:
limits:
memory: 256M Security Best Practices
1. Strong Authentication
# Use strong passwords with htpasswd
htpasswd -B config/users username
# Regularly rotate passwords
# Remove old users
htpasswd -D config/users olduser 2. Network Isolation
# In docker-compose.yml
services:
radicale:
networks:
- radicale-internal
# Don't expose ports directly if using reverse proxy
networks:
radicale-internal:
internal: true 3. Regular Updates
# Update Radicale container
docker-compose pull radicale
docker-compose up -d radicale
# Check for security advisories
curl -s https://api.github.com/repos/Kozea/Radicale/releases/latest 4. Firewall Configuration
# Only allow connections from reverse proxy
sudo ufw allow from 172.18.0.0/16 to any port 5232
# If exposing directly, limit connections
sudo ufw limit 5232/tcp 5. Fail2Ban Integration
Create /etc/fail2ban/filter.d/radicale.conf:
[Definition]
failregex = ^.* login failed for user <HOST>.*$
ignoreregex = Create /etc/fail2ban/jail.d/radicale.conf:
[radicale]
enabled = true
port = http,https
filter = radicale
logpath = /home/user/radicale/data/radicale.log
maxretry = 5
bantime = 3600 Migrating from Other Services
From Google Calendar/Contacts
Export data from Google:
- Calendar: Google Calendar > Settings > Import & Export > Export
- Contacts: Google Contacts > Export > vCard format
Import to Radicale:
- Access web interface:
https://cal.yourdomain.com - Create new calendar/address book
- Upload .ics or .vcf files
- Access web interface:
From iCloud
- Export calendars from iCloud web interface
- Import files through Radicale web interface
- Configure devices to sync with Radicale instead of iCloud
Maintenance
Regular Maintenance Tasks
# Weekly: Check logs for errors
docker-compose logs radicale | grep -i error
# Monthly: Verify backups
ls -lh /mnt/backups/radicale/
# Monthly: Clean old logs if enabled
find ~/radicale/data -name "*.log" -mtime +30 -delete
# Quarterly: Update container
docker-compose pull radicale && docker-compose up -d radicale Health Monitoring
Create a monitoring script:
#!/bin/bash
# Check Radicale health
# Test HTTP response
if curl -f -s -u username:password https://cal.yourdomain.com/ > /dev/null; then
echo "Radicale is healthy"
else
echo "Radicale is down!"
# Send alert (email, ntfy, etc.)
fi
# Check disk space
DISK_USAGE=$(df -h ~/radicale/data | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 80 ]; then
echo "Warning: Disk usage above 80%"
fi Conclusion
Congratulations! You now have a fully functional Radicale server providing:
- Self-hosted calendar and contacts
- Cross-platform device synchronization
- Complete privacy and data ownership
- Minimal resource consumption
- Simple backup and maintenance
Next Steps
- Configure all your devices to use Radicale
- Set up automated backups
- Create shared calendars for family or team
- Monitor logs and performance
- Explore advanced authentication options
Resources
Your personal calendar and contacts server is now ready. Enjoy the freedom of self-hosted scheduling and contacts management!