Web Security Hardening
ThingsBoard ships with a permissive cross-origin resource sharing (CORS) policy and with the X-Frame-Options and Content-Security-Policy headers turned off. These defaults keep dashboards embedded in iframes, multi-domain deployments, and custom widgets working without extra configuration. If your security policy or an external audit requires stricter settings, use the environment variables on this page to restrict cross-origin access and enable browser security headers.
Restrict cross-origin requests
Section titled “Restrict cross-origin requests”The CORS policy applies to the REST API (/api/**). By default, ThingsBoard accepts cross-origin requests from any origin and responds with Access-Control-Allow-Credentials: true. Security scanners can report this configuration as “arbitrary origin trusted”.
| Variable | Default | Description |
|---|---|---|
TB_CORS_ALLOWED_ORIGIN_PATTERNS |
* |
Comma-separated list of origins allowed to call the REST API. * allows any origin. |
TB_CORS_ALLOWED_METHODS |
* |
Comma-separated list of allowed HTTP methods. |
TB_CORS_ALLOWED_HEADERS |
* |
Comma-separated list of allowed request headers. |
TB_CORS_MAX_AGE |
1800 |
How long, in seconds, browsers cache the response to a preflight request. |
TB_CORS_ALLOW_CREDENTIALS |
true |
Whether cross-origin requests can include credentials. |
To restrict access, set TB_CORS_ALLOWED_ORIGIN_PATTERNS to the origins you trust. Include the public address of your ThingsBoard instance and every external site that calls the REST API from a browser:
TB_CORS_ALLOWED_ORIGIN_PATTERNS=https://iot.example.com,https://portal.example.comEach origin consists of a scheme, a host, and an optional port, like https://iot.example.com or http://192.168.1.10:8080. ThingsBoard rejects browser requests from any other origin with 403 Forbidden. Requests without an Origin header, like scripts that call the REST API from a server, are not affected.
Enable HTTP security headers
Section titled “Enable HTTP security headers”ThingsBoard can add four security headers to its HTTP responses. Two of them are enabled by default. The other two are disabled because they can block iframe embedding and custom widget content.
| Header | Enabled by default | Variables |
|---|---|---|
X-Content-Type-Options: nosniff |
Yes | SECURITY_HEADERS_X_CONTENT_TYPE_OPTIONS_ENABLED |
Referrer-Policy |
Yes | SECURITY_HEADERS_REFERRER_POLICY_ENABLED, SECURITY_HEADERS_REFERRER_POLICY_VALUE (default strict-origin-when-cross-origin) |
X-Frame-Options |
No | SECURITY_HEADERS_X_FRAME_OPTIONS_ENABLED, SECURITY_HEADERS_X_FRAME_OPTIONS_VALUE (default SAMEORIGIN) |
Content-Security-Policy |
No | SECURITY_HEADERS_CONTENT_SECURITY_POLICY_ENABLED, SECURITY_HEADERS_CONTENT_SECURITY_POLICY_VALUE, SECURITY_HEADERS_CONTENT_SECURITY_POLICY_REPORT_ONLY |
X-Content-Type-Options stops browsers from guessing the content type of a response. Referrer-Policy controls how much of the page URL the browser sends to other sites. The default strict-origin-when-cross-origin value matches the browser’s built-in behavior, so keeping both headers enabled does not change how ThingsBoard works.
X-Frame-Options
Section titled “X-Frame-Options”X-Frame-Options protects against clickjacking by controlling which pages can load ThingsBoard in an iframe. It accepts two values:
- SAMEORIGIN: Allows iframes only on pages from the same origin (scheme, host, and port) as ThingsBoard.
- DENY: Blocks all iframe embedding, including dashboards embedded on your own sites.
ThingsBoard treats any other value as SAMEORIGIN and logs a warning.
SECURITY_HEADERS_X_FRAME_OPTIONS_ENABLED=trueSECURITY_HEADERS_X_FRAME_OPTIONS_VALUE=SAMEORIGINContent-Security-Policy
Section titled “Content-Security-Policy”Content-Security-Policy (CSP) restricts which scripts, styles, images, and other resources the browser loads. This mitigates cross-site scripting and data injection attacks. When you enable CSP without setting SECURITY_HEADERS_CONTENT_SECURITY_POLICY_VALUE, ThingsBoard uses a built-in policy that covers OpenStreetMap and ArcGIS maps, solution template previews, WebSocket telemetry, and chart Web Workers:
default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://img.thingsboard.io https://tile.openstreetmap.org https://*.tile.openstreetmap.org https://*.arcgis.com; font-src 'self' data:; connect-src 'self' ws: wss: https://*.arcgis.com https://*.arcgisonline.com; worker-src 'self' blob:; frame-ancestors 'self'Adjust the policy to match your dashboards:
- Google Maps: Append
https://maps.googleapis.com https://maps.gstatic.comtoscript-src. The built-in policy includes no Google domains in any directive, so the map can also need Google domains inimg-srcandconnect-src. Use the report-only rollout below to find the full list. - Embedding on other sites: The
frame-ancestors 'self'directive blocks dashboards embedded on a site with a different origin. Add the embedding sites toframe-ancestors. - Custom widgets: A stricter
script-srclike'self'alone breaks HTML Card widgets with inline JavaScript, custom widgets with inline scripts or styles, and widgets that load external images, fonts, or scripts.
SECURITY_HEADERS_CONTENT_SECURITY_POLICY_VALUE replaces the whole built-in policy, so copy the built-in value and change only the directives you need.
To roll out CSP without breaking dashboards, test it in report-only mode first:
-
Set
SECURITY_HEADERS_CONTENT_SECURITY_POLICY_ENABLED=trueandSECURITY_HEADERS_CONTENT_SECURITY_POLICY_REPORT_ONLY=true, then restart ThingsBoard. The browser reports policy violations without blocking anything. -
Open your dashboards and check the browser console for CSP violation messages.
-
Add the blocked sources to
SECURITY_HEADERS_CONTENT_SECURITY_POLICY_VALUEand restart ThingsBoard. -
Set
SECURITY_HEADERS_CONTENT_SECURITY_POLICY_REPORT_ONLY=falseand restart ThingsBoard. The browser now enforces the policy.
Microservice deployments
Section titled “Microservice deployments”In a microservice deployment, set these variables on two services:
- tb-node: Applies the CORS policy and adds the security headers to REST API responses. Set
TB_CORS_*andSECURITY_HEADERS_*on this service. - web-ui: Serves the web interface and adds the security headers to its own responses. Set the same
SECURITY_HEADERS_*variables on this service. The web-ui service has no CORS settings.
The web-ui service handles two settings differently from tb-node:
- CSP needs an explicit value: When you set
SECURITY_HEADERS_CONTENT_SECURITY_POLICY_ENABLED=trueon web-ui, also setSECURITY_HEADERS_CONTENT_SECURITY_POLICY_VALUEon web-ui. The web-ui service has no built-in policy, so without a value it sends no Content-Security-Policy header. - X-Frame-Options value is not checked: With
SECURITY_HEADERS_X_FRAME_OPTIONS_ENABLED=true, web-ui sendsSECURITY_HEADERS_X_FRAME_OPTIONS_VALUE(defaultSAMEORIGIN) unchanged. Unlike tb-node, it does not replace other values withSAMEORIGIN, so useDENYorSAMEORIGINonly.
Apply the configuration
Section titled “Apply the configuration”Set the variables the same way as any other ThingsBoard configuration parameter. See How to change configuration for all deployment types, including Windows and Kubernetes.
Add the variables to /usr/share/thingsboard/conf/thingsboard.conf:
export TB_CORS_ALLOWED_ORIGIN_PATTERNS=https://iot.example.com,https://portal.example.comexport SECURITY_HEADERS_X_FRAME_OPTIONS_ENABLED=trueRestart ThingsBoard:
sudo systemctl restart thingsboardAdd the variables to the environment section of the ThingsBoard service in docker-compose.yml:
environment: TB_CORS_ALLOWED_ORIGIN_PATTERNS: "https://iot.example.com,https://portal.example.com" SECURITY_HEADERS_X_FRAME_OPTIONS_ENABLED: "true"Apply the updated configuration:
docker compose up -dVerify the configuration
Section titled “Verify the configuration”Replace https://iot.example.com with the address of your ThingsBoard instance.
-
Send a request from an untrusted origin:
Terminal window curl -s -o /dev/null -D - -H "Origin: https://untrusted.example" https://iot.example.com/api/noauth/userPasswordPolicyThe response status is
403and the response has noAccess-Control-Allow-Originheader. -
Send the same request from an external origin in
TB_CORS_ALLOWED_ORIGIN_PATTERNS, likehttps://portal.example.com. Do not use the address of your ThingsBoard instance: ThingsBoard usually treats a request from its own address as same-origin and adds no CORS headers. If you allowed only the address of your ThingsBoard instance, skip this step.Terminal window curl -s -o /dev/null -D - -H "Origin: https://portal.example.com" https://iot.example.com/api/noauth/userPasswordPolicyThe response contains
Access-Control-Allow-Origin: https://portal.example.com. -
Check the security headers on the login page:
Terminal window curl -s -o /dev/null -D - https://iot.example.com/The response contains every header you enabled.
-
Log in to ThingsBoard and open your dashboards, including dashboards embedded on other sites. Confirm that they load without errors.
Earlier versions
Section titled “Earlier versions”The TB_CORS_* and SECURITY_HEADERS_* variables are available in ThingsBoard 4.2.2.1, 4.3.1.1, and later. In earlier versions, thingsboard.yml sets a CORS policy that allows any origin, with no environment variable to override it, and ThingsBoard does not send these security headers. To restrict CORS, upgrade ThingsBoard or edit the spring.mvc.cors section of thingsboard.yml. To add the security headers, upgrade ThingsBoard or set them on your reverse proxy.
See also
Section titled “See also”Was this helpful?