Skip to content


Baïkal is a lightweight CalDAV+CardDAV server. It offers an extensive web interface with easy management of users, address books and calendars. Baïkal allows to seamlessly access your contacts and calendars from every device. It is compatible with iOS, Mac OS X, DAVx5 on Android, Mozilla Thunderbird and every other CalDAV and CardDAV capable application.

Install the files

Drop into a sudo shell session and make the installation directory:

sudo -s
mkdir /var/www/

Move into the new directory and download the latest release of Baikal:

cd /var/www/

Unzip the file and remove the original:

unzip -d ./

Create the user for the site:

groupadd www-dav
useradd -g www-dav www-dav

Create a temp directory and set the permissions:

mkdir -p /var/www/
chown -R www-dav:www-dav baikal
chmod 755 baikal
find /var/www/ -type f -exec chmod 600 {} \;
find /var/www/ -type d -exec chmod 700 {} \;
chown -R www-dav:www-data /var/www/
find /var/www/ -type f -exec chmod 640 {} \;
find /var/www/ -type f -name '*.php' -exec chmod 600 {} \;
find /var/www/ -type d -exec chmod 750 {} \;
find /var/www/ -type d -exec chmod g+s,u+s {} \;

Create a database

Open MariaDB:

mysql -u root -p

Create the database and user:

GRANT ALL PRIVILEGES ON baikal.* TO 'baikal_user'@'localhost' IDENTIFIED BY '<your-db-password>';

Create the Apparmor profiles

Create the PHP pool profile:

nano /etc/apparmor.d/php-fpm.d/dav

Enter the following:

  ^dav flags=(attach_disconnected, complain) {
    #include <abstractions/base>
    #include <abstractions/nameservice>
    #include <abstractions/openssl>
    #include <abstractions/php>
    #include <abstractions/ssl_certs>

    signal receive peer=php-fpm7.4,

    deny /usr/share/{phpmyadmin,postfixadmin}/** r,
    deny /var/www/{,webmail.,video.,audio.}** r,

    /run/mysqld/mysqld.sock rwlk,
    @{run}/php/php7.4-fpm-www-dav.sock rwlk,
    owner /var/www/** r,
    /etc/mime.types r,

Create the Apache profile:

  nano /etc/apparmor.d/apache2.d/dav-a2

Enter the following:

    ^dav-a2 flags=(attach_disconnected) {
    #include <abstractions/apache2-common>
    #include <abstractions/base>
    #include <abstractions/nameservice>
    #include <abstractions/php>

    capability setuid,
    capability setgid,

    # for log writing (could be abstracted)
    /var/log/apache2/access.log w,
    /var/log/apache2/error.log w,

    # Socket access
    /run/php/php7.4-fpm-www-dav.sock wr,

    # Access to standard Baikal files
    /var/www/ r,
    /var/www/** r,

    # Deny access to these locations
    deny /usr/share/{phpmyadmin,postfixadmin}/** r,
    deny /var/www/{,audio.,video.,webmail.}** r,
    deny /var/www/**.{php,sample} r,
    deny /var/www/{Core,Specific}/** r,

    # Deny access to Bash
    deny /bin/bash r,
    deny /bin/dash r,

Reload Apparmor:

 service apparmor reload

Monitor with aa-logprof and once happy, enforce the profile with aa-enforce


Create the FPM pool:

 nano /etc/php/7.4/fpm/pool.d/www-dav

Enter the following:

 ; pool name

; Unix user/group of processes
; will be used.
user = www-dav
group = www-dav
listen.owner = www-data = www-data
listen.mode = 0660

; Socket to which the Apache will connect
listen = /run/php/php7.4-fpm-www-dav.sock

apparmor_hat = dav

pm = ondemand

pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 500

php_admin_value[allow_url_fopen] = On
php_admin_value[allow_url_include] = On
php_admin_value[memory_limit] = 512M
php_admin_flag[output_buffering] = Off
php_admin_value[max_execution_time] = 1800
php_admin_value[max_input_time] = 3600
php_admin_value[post_max_size] = 10M
php_admin_value[upload_max_filesize] = 10M
php_admin_value[upload_tmp_dir] = /var/www/
php_admin_value[sys_temp_dir] = /var/www/
php_admin_value[max_file_uploads] = 20
php_admin_flag[session.cookie_secure] = True
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
php_admin_value[open_basedir] = /var/www/

Restart PHP-FPM

service php7.4-fpm restart

Create certificates

Create RSA Certificates with:

~/ --issue --dns dns_cloudns -d --keylength 4096 --key-file /etc/letsencrypt/rsa-certs/ --ca-file /etc/letsencrypt/rsa-certs/ --cert-file /etc/letsencrypt/rsa-certs/ --fullchain-file /etc/letsencrypt/rsa-certs/ --pre-hook "mkdir -p /etc/letsencrypt/rsa-certs/" --post-hook "find /etc/letsencrypt/rsa-certs/ -name '*.pem' -type f -exec chmod 600 {} \;" --renew-hook "find /etc/letsencrypt/rsa-certs/ -name '*.pem' -type f -exec chmod 600 {} \; -exec service nginx reload \;"

and ECC certificates with:

~/ --issue --dns dns_cloudns -d --keylength ec-384 --key-file /etc/letsencrypt/ecc-certs/ --ca-file /etc/letsencrypt/ecc-certs/ --cert-file /etc/letsencrypt/ecc-certs/ --fullchain-file /etc/letsencrypt/ecc-certs/ --pre-hook "mkdir -p /etc/letsencrypt/ecc-certs/" --post-hook "find /etc/letsencrypt/ecc-certs/ -name '*.pem' -type f -exec chmod 600 {} \;" --renew-hook "find /etc/letsencrypt/ecc-certs/ -name '*.pem' -type f -exec chmod 600 {} \; -exec service nginx reload \;"

Create Apache2 virtualhost

Create the file:

nano /etc/apache2/sites-available/

Enter the following:


    DocumentRoot /var/www/

    <IfModule mod_apparmor.c>
        AADefaultHatName dav-a2

    <Directory "/var/www/">
        Options None
        AllowOverride None
        Require all granted

    <IfModule mod_expires.c>
        ExpiresActive Off

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php7.4-fpm-www-dav.sock|fcgi://localhost/"

    SetEnvIf HTTPS on HTTPS=on

    RemoteIPHeader X-Forwarded-For

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined


Enable the site:


Reload the service:

service apache2 reload


Now create the Nginx site:

nano /etc/nginx/sites-available/

Enter the following:

    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    ssl_certificate /etc/letsencrypt/ecc-certs/;
    ssl_certificate_key /etc/letsencrypt/ecc-certs/;
    ssl_certificate /etc/letsencrypt/rsa-certs/;
    ssl_certificate_key /etc/letsencrypt/rsa-certs/;
    ssl_trusted_certificate /etc/letsencrypt/ecc-certs/;

    include /etc/nginx/custom-config/ssl.conf;
    include /etc/nginx/custom-config/header.conf;

    # Nginx Bad Bot Blocker Includes
    include /etc/nginx/bots.d/ddos.conf;
    include /etc/nginx/bots.d/blockbots.conf;

    # Block Bad Countries
    if ($allowed_country = no) {
        set $blockreason '[geo_blocked]';
        return 403;

    # 7g Firewall
    if ( $bad_querystring !~* "\[OK\]" ) {
        set $blockreason $bad_querystring;
        return 403;

    if ( $bad_request !~* "\[OK\]" ) {
        set $blockreason $bad_request;
        return 403;

    if ( $bad_request_method !~* "\[OK\]||\[bad_request_method_rule_1\]" ) {
        set $blockreason $bad_request_method;
        return 403;

    rewrite ^/.well-known/caldav /dav.php redirect;
    rewrite ^/.well-known/carddav /dav.php redirect;

    charset utf-8;

    location ~ /(\.ht|Core|Specific) {
      deny all;
      return 404;

    location / {
        include /etc/nginx/custom-config/proxy.conf;
        client_max_body_size 100M;

Enable the site:

ln -s /etc/nginx/sites-available/ /etc/nginx/sites-available/

Reload Nginx

service nginx reload

Check for errors:

nginx -t

Finish configuration

Now browse to your new site at to complete the setup. You will need the database details from the database you set up earlier (the server will be 'localhost'). For the authntication type, select 'Digest'.

Once complete, edit the configuration file:

nano /var/www/

Change the base_uri value:

    base_uri: '/'