Hi @prabir , Camunda 8.8 does not expose a first-class “classic CORS” allow-list property (i.e. there is no Access-Control-Allow-Origin / allowed-origins setting) for the Orchestration Cluster REST API (/v2 on the Zeebe Gateway). What it does expose is the family of cross-origin isolation headers (COEP / COOP / CORP) plus a pluggable REST filter mechanism. For real browser CORS you either add a custom servlet filter to the gateway or (recommended) terminate CORS at a reverse proxy in front of Camunda.
1. Does 8.8 support configuring CORS for the Orchestration Cluster REST API?
- Not as classic CORS. There is no built-in property that emits
Access-Control-Allow-Origin, Access-Control-Allow-Methods, etc. for the /v2 REST API.
- The Orchestration Cluster does let you configure the cross-origin HTTP headers COEP/COOP/CORP via
camunda.security.http-headers.*. These control browser embedding/isolation — they are not the same as CORS Access-Control-Allow-* headers, so setting them will not by itself fix a browser CORS preflight failure.
- The gateway REST server is a Spring Boot application, and it supports a documented REST filter extension point (
camunda.api.rest.filters) that you can use to inject a jakarta.servlet.Filter implementing CORS.
2. Recommended way to enable CORS with c8run
Option A — Reverse proxy (recommended for anything beyond local dev). This is Camunda’s standard recommendation: put a reverse proxy (nginx, Apache, Spring Cloud Gateway, an API gateway, etc.) in front of the cluster and let it add the Access-Control-Allow-* headers and answer OPTIONS preflight. This keeps the Camunda API itself off the public internet.
Option B — Custom REST filter loaded into the gateway. The gateway supports loading arbitrary Jakarta servlet filters that apply only to the Orchestration Cluster REST API (they do not affect gRPC). You compile a small CORS filter into a JAR and reference it from application.yaml. Since c8run runs the standalone/embedded gateway, this is the in-process way to get CORS headers on /v2.
For c8run, put your settings in an application.yaml and start with:
# macOS / Linux
./start.sh --config application.yaml
# Windows
c8run.exe start --config application.yaml
(If changes don’t take effect, pass --config explicitly and fully restart — c8run only reliably picks up custom config this way.)
3. Specific properties
Cross-origin isolation headers (COEP/COOP/CORP) — available, but not classic CORS:
Equivalent env vars: CAMUNDA_SECURITY_HTTPHEADERS_CROSSORIGINEMBEDDERPOLICY_VALUE, ..._CROSSORIGINOPENERPOLICY_VALUE, ..._CROSSORIGINRESOURCEPOLICY_VALUE.
Custom REST filter (the real CORS lever):
Env-var form: CAMUNDA_API_REST_FILTERS_0_ID, CAMUNDA_API_REST_FILTERS_0_JARPATH, CAMUNDA_API_REST_FILTERS_0_CLASSNAME.
4. Example configuration
(a) c8run application.yaml setting the cross-origin isolation headers (so a browser can embed/load resources cross-origin):
camunda:
security:
http-headers:
cross-origin-embedder-policy:
value: UNSAFE_NONE
cross-origin-opener-policy:
value: SAME_ORIGIN_ALLOW_POPUPS
cross-origin-resource-policy:
value: CROSS_ORIGIN
(b) c8run application.yaml wiring a custom CORS servlet filter into the gateway REST API:
camunda:
api:
rest:
filters:
- id: cors-filter
jar-path: /opt/camunda/filters/cors-filter.jar
class-name: io.example.CorsFilter
Minimal filter implementation (compile against jakarta.servlet-api, JDK 21 or lower, package as cors-filter.jar):
package io.example;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public final class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse http = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
// Reflect/allow only the origins you trust
http.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
http.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
http.setHeader("Access-Control-Allow-Headers", "Authorization,Content-Type,Origin,Accept");
http.setHeader("Access-Control-Allow-Credentials", "true");
http.setHeader("Access-Control-Max-Age", "3600");
// Short-circuit preflight
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
http.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
chain.doFilter(req, res);
}
}
Compile example:
javac -classpath lib/jakarta.servlet-api.jar CorsFilter.java
jar cf cors-filter.jar io/example/CorsFilter.class
(c) Reverse proxy alternative (nginx sketch) — the approach Camunda generally recommends:
location /v2/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "http://localhost:3000";
add_header Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,OPTIONS";
add_header Access-Control-Allow-Headers "Authorization,Content-Type,Origin,Accept";
add_header Access-Control-Max-Age 3600;
return 204;
}
add_header Access-Control-Allow-Origin "http://localhost:3000" always;
proxy_pass http://localhost:8080;
}
Important caveats / notes
- COEP/COOP/CORP ≠ CORS. If your issue is a browser blocking
fetch/XHR from a different origin with a CORS error, the http-headers properties alone will not solve it — you need the servlet filter (Option B) or the proxy (Option A).
SPRING_MVC_CORS_MAPPINGS_* does not apply here. Those spring.mvc.cors.* mappings work for the classic Management Identity Spring MVC app, but the Orchestration Cluster gateway REST server is a different (reactive) Spring Boot server, so those MVC CORS mappings are not the mechanism for the /v2 API.
- Best practice: don’t expose the Orchestration Cluster REST API directly to browsers/end users. Front it with your own backend/API layer or reverse proxy and enable authentication (
camunda.security.authentication.unprotected-api: false and camunda.security.authorizations.enabled: true) — CORS is then handled at that edge.