1
CORS
n edited this page 2026-08-08 16:39:04 +02:00
To make CORS work for a self-hosted calendar like Baikal, that doesn't support it by itself, you can let your webserver handle the respective OPTIONS requests.
Caddy
I use Caddy and Baikal runs as a container via podman, so it is connected via reverse_proxy. My configuration looks like this:
dav.koehr.ing {
route {
@denied path_regexp ^/(\.ht|Core|Specific)
respond @denied 404
@preflight {
method OPTIONS
header Access-Control-Request-Method *
}
header @preflight {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK"
Access-Control-Allow-Headers "User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With"
Access-Control-Max-Age "1728000"
}
respond @preflight 204
redir /.well-known/carddav* /dav.php 308
redir /.well-known/caldav* /dav.php 308
@cors {
header Origin *
}
header @cors {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK"
Access-Control-Allow-Headers "User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With"
Access-Control-Expose-Headers "Etag,Preference-Applied"
}
reverse_proxy * localhost:10500
}
}
It is important to keep everything inside the route block, otherwise Caddy internally sorts redir before the preflight rules.
Nginx
For your convenience, this is a copy of an Nginx config I found here. I didn't try it, though:
# Based on http://sabre.io/baikal/install/
# If you use different hostnames for Baikal and Infcloud,
# e.g. dav.mydomain.org and infcloud.mydomain.org, then
# replace "*" in "Access-Control-Allow-Origin" with your
# hostname where Infcloud runs, e.g. infcloud.mydomain.com.
server {
listen 80;
server_name _;
root /var/www/baikal/html;
index index.php;
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;
}
# Pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ ^(.+\.php)(.*)$ {
try_files $fastcgi_script_name =404;
include /etc/nginx/fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Add CORS support, e.g. for InfCloud
# See
# - https://enable-cors.org/server_nginx.html
# - https://www.inf-it.com/infcloud/readme.txt section 3
# - https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
# cors_flags is 0 : Not a browser request, not a preflight request
# cors_flags is 1 : Browser request, not a preflight request
# cors_flags is 11: Preflight request sent by a browser
set $cors_flags 0;
if ($http_user_agent ~ "Mozilla") {
set $cors_flags 1;
}
if ($request_method = 'OPTIONS') {
set $cors_flags "${cors_flags}1";
}
# Browser preflight request: add CORS headers and don't forward to Baikal
if ($cors_flags = 11) {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK' always;
add_header 'Access-Control-Allow-Headers' 'User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With' always;
# Tell client that this pre-flight info is valid for 20 days
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# From browser, but not a preflight request: add CORS headers and forward to Baikal
if ($cors_flags = 1) {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK' always;
add_header 'Access-Control-Allow-Headers' 'User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With' always;
add_header 'Access-Control-Expose-Headers' 'Etag,Preference-Applied' always;
}
}
}