Its Working!
Prerequisites
- A HestiaCP account named
<user>
.
- A web domain
<domain>
already configured in HestiaCP.
- Your server’s public IP:
<server-ip>
.
- Root or sudo access on the host.
1. Prepare data and config directories
# 1.1 Create calendar-data folder (will hold collections)
mkdir -p /home/<user>/web/<domain>/radicale-data/collections
# 1.2 Create Radicale config folder
mkdir -p /home/<user>/web/<domain>/radicale-config
# 1.3 Ensure Hestia user owns both
chown -R 1004:1004 /home/<user>/web/<domain>/radicale-data
chown -R 1004:1004 /home/<user>/web/<domain>/radicale-config
Note: Replace 1004:1004
with your HestiaCP user’s UID:GID if different.
2. Write the Radicale configuration
Create /home/<user>/web/<domain>/radicale-config/config
with these contents:
[server]
hosts = 0.0.0.0:5232
[storage]
filesystem_folder = /data/collections
[auth]
type = none
Set ownership and permissions:
chown 1004:1004 /home/<user>/web/<domain>/radicale-config/config
chmod 640 /home/<user>/web/<domain>/radicale-config/config
3. Pull the Radicale Docker image
docker pull tomsquest/docker-radicale:latest
4. Run the Radicale container
docker run -d \
--name radicale \
--user 1004:1004 \
-p 127.0.0.1:5232:5232 \
--read-only \
--init \
--security-opt no-new-privileges:true \
--cap-drop ALL \
--cap-add CHOWN \
--cap-add SETUID \
--cap-add SETGID \
--cap-add KILL \
--pids-limit 50 \
--memory 256M \
--health-cmd="curl --fail http://localhost:5232 || exit 1" \
--health-interval=30s \
--health-retries=3 \
-v /home/<user>/web/<domain>/radicale-data:/data \
-v /home/<user>/web/<domain>/radicale-config:/config:ro \
tomsquest/docker-radicale:latest
Verify operation:
docker ps --filter name=radicale
docker logs radicale
curl -L http://127.0.0.1:5232/.web/
5. Create HestiaCP Nginx “custom include” files
HestiaCP will automatically include any file in /home/<user>/conf/web/<domain>/
matching nginx.conf_*
or nginx.ssl.conf_*
.
5.1 HTTP proxy (nginx.conf_custom
)
File:
/home/<user>/conf/web/<domain>/nginx.conf_custom
# Redirect root to Radicale UI
location = / {
return 302 /.web/;
}
# Proxy everything under /.web/ to Radicale
location ^~ /.web/ {
proxy_pass http://127.0.0.1:5232;
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_read_timeout 360s;
}
5.2 HTTPS proxy (nginx.ssl.conf_custom
)
File:
/home/<user>/conf/web/<domain>/nginx.ssl.conf_custom
location = / {
return 302 /.web/;
}
location ^~ /.web/ {
proxy_pass http://127.0.0.1:5232;
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_read_timeout 360s;
}
6. Reload Nginx / Rebuild the domain
nginx -t
systemctl reload nginx
7. Verify end-to-end
-
HTTP → HTTPS redirect
curl -i -H "Host: <domain>" http://<server-ip>:80/
You should get a 301
to https://<domain>/
.
-
Radicale UI via domain
curl -L -i -H "Host: <domain>" http://<server-ip>:80/
→ 302
to /.web/
, then 200 OK
with the Radicale interface HTML.
-
Browser test
Open
https://www.<domain>/
You should see Radicale’s UI instead of the “Under Construction” placeholder.
That’s it! This setup runs Radicale in Docker as your HestiaCP user, stores data under your web folder, and proxies all /
→ /.web/
traffic through Nginx.