Harden your deployment with isolation and network controls
For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append.mdto anykestra.io/docs/*URL for plain Markdown.
Secure a Kestra deployment with credential setup, network and worker isolation, transport security, request filtering, and endpoint protection.
Credential initialization
On Enterprise Edition, use OIDC/SSO or LDAP instead of Basic Authentication. These integrate with your existing identity provider, support MFA, and remove the risk of locally managed credentials. One-Time-Password (OTP) is also supported, though it provides less protection than SSO with MFA.
If you use Basic Authentication on OSS or EE, configure username and password in the application configuration file before you start Kestra in production. Kestra then skips the Setup page.
kestra: server: basic-auth: username: admin@kestra.io password: "{{ your-strong-password }}"On a fresh installation with no basic-auth credentials configured, the Setup page at /ui/main/setup is publicly reachable. Any user who reaches it first can set credentials and lock out the intended administrator.
Network isolation
By design, Kestra allows arbitrary HTTP calls and script execution. To prevent misuse of link-local metadata services (IMDS), isolate and block access at the network layer:
- Network ACLs or security groups: configure your VPC or firewall to deny all requests to link-local ranges (e.g.,
169.254.169.254/32). - Dedicated orchestration subnet: place Kestra workers in a private subnet with no route to management or metadata services.
- Egress proxy or NAT gateway filtering: route all outbound traffic through a proxy or gateway that can enforce allow-lists and block link-local IPs.
Host-level isolation
Running workflows in isolated environments reduces the impact of potentially malicious flows:
- Container sandboxes: launch each flow execution in its own container (for example, Docker or Kubernetes Pod) with minimal privileges.
- Ephemeral compute: use Kestra’s native Task Runners to auto-scale ephemeral compute nodes that are destroyed after each run, leaving no residual state.
- Minimum host permissions: grant only the OS-level rights required for the runtime; avoid mounting cloud credential files or granting host-level IAM roles directly.
Worker isolation
In Kestra 2.0, workers do not connect to the database. A worker opens an outbound gRPC stream to a Worker Controller, receives jobs on that stream, and sends results, logs, and metrics back on it. The database repositories are not on the worker classpath, so there is no worker-side setting that can re-enable direct database access. See worker communication for how the stream works.
This changes what a compromised worker can reach:
- A worker holds no database credentials. An attacker who takes over a worker host gets the jobs that worker was given, not the tables holding every execution and flow.
- Secrets travel encrypted on the job queue and on the gRPC stream. The worker decrypts a secret at the moment a task uses it, and the plaintext is never written back into the execution.
- Worker gRPC traffic is flagged as internal, so IAM does not treat it as a user API call.
Because the worker only needs an outbound route to the controller, you can run it in a restricted subnet, a separate region, or an air-gapped site. Give it network access to the controller port and to the systems its tasks call, and nothing else. Do not give worker hosts database credentials or cloud IAM roles that the tasks themselves do not need.
The gRPC channel is plaintext and unauthenticated by default. Before a worker runs outside the network you trust, secure the channel as described in the next section.
Transport security
In distributed deployments, Worker Controllers communicate with Workers over gRPC. By default, this channel is plaintext and unauthenticated: any process that can reach the controller port can register as a worker, receive tasks, read KV and namespace file metadata, and send task results back. The controller also sends each worker the instance’s encryption key (kestra.encryption.secret-key) when it connects, so an unauthenticated worker can decrypt SECRET values.
Basic authentication (OSS)
The open-source edition has no TLS on this channel, so keep controllers and workers on a trusted network. To stop unknown processes from connecting as workers, require a shared username and password:
kestra: grpc: basic-auth: enabled: true username: kestra-worker password: ${KESTRA_GRPC_PASSWORD}Set the same values on every worker and on every server that runs a controller. Webserver and standalone servers start an embedded controller unless you pass --no-controller, so they need the credentials too.
The credentials authenticate the worker fleet as a whole. They don’t identify an individual worker or assign it to a worker group, and they’re separate from kestra.server.basic-auth.
Basic authentication doesn’t encrypt the channel. Every call carries the credentials base64-encoded, so anyone who can capture traffic between workers and the controller can read them.
Health checks (grpc.health.v1.Health) don’t require credentials, so orchestrator liveness probes keep working. To change the credentials, update them on every server, then restart the controllers and the workers.
To confirm the setup, check that the controller logs gRPC basic authentication is enabled at startup and that each worker logs Connected to controller. A worker with missing or wrong credentials doesn’t start, and logs this error:
Failed connecting to Kestra controller (UNAUTHENTICATED): Missing or invalid basic authentication credentials. Configure kestra.grpc.basic-auth.username and kestra.grpc.basic-auth.password on this worker with the values expected by the controller.See gRPC basic authentication for the property reference.
TLS and worker authentication (EE)
Enterprise Edition encrypts the channel with TLS and can verify each worker’s identity:
- One-way TLS — the controller presents a certificate; workers verify it. Encrypts the channel without requiring worker certificates.
- Mutual TLS (mTLS) — both controller and worker present certificates. Use this when you need strong identity verification between components, not just encryption.
See gRPC TLS/mTLS configuration for setup instructions and a full property reference. With mTLS (kestra.grpc.tls.client-auth: REQUIRE), a worker without a certificate signed by the controller’s trusted CA is refused at the handshake.
On top of transport security, Enterprise Edition workers can be required to present a JWT obtained with a per-group registration token (kestra.ee.worker.auth.enabled, off by default). See worker authentication.
kestra.grpc.basic-auth has no effect on Enterprise Edition. Use worker authentication instead.
HTTP task URL filtering
HTTP plugin tasks (Request, Download, SseRequest, and Trigger) make server-side HTTP calls to URIs controlled by flow authors. Without restrictions, a flow author can reach cloud metadata endpoints (such as 169.254.169.254 on AWS, GCP, and Azure), internal management APIs, or private services not reachable from the internet.
Configure an allow-list, a deny-list, or both under kestra.tasks.http:
kestra: tasks: http: allowed-list: - https://api.example.com - https://*.data.partner.io # matches foo.data.partner.io, bar.data.partner.io, etc. denied-list: - 169.254.0.0/16 # link-local range (AWS/GCP/Azure IMDS), all schemes - http://localhost - http://127.0.0.1| Property | Default | Description |
|---|---|---|
kestra.tasks.http.allowed-list | [] | When non-empty, a request URI must match at least one entry or the task fails. |
kestra.tasks.http.denied-list | [] | A request URI that matches any entry causes the task to fail. Evaluated after the allowed-list. |
Both lists are empty by default. No filtering is applied unless you configure them.
When both lists are set, the allowed-list is checked first. A URI that matches an allowed-list entry but also matches a denied-list entry is still blocked.
allowed-list is the enforcement boundary; denied-list is best-effort. A hostname in the denied-list can be made to resolve to an IP that is not blocked, bypassing the check. The allowed-list cannot be bypassed this way: only entries that explicitly match are permitted. For strong SSRF protection, prefer an allowed-list over a denied-list.
Matching rules
Host matching is exact by default. The scheme and port must also match when specified. The path is prefix-matched.
https://api.example.commatcheshttps://api.example.com/v1/databut nothttps://sub.api.example.com/v1/data.http://169.254.169.254blockshttp://169.254.169.254/latest/meta-data/....
Wildcard subdomain matching: prefix an entry with *. to match all subdomains of a hostname. Wildcards do not apply to IP addresses.
*.example.com(orhttps://*.example.com) matchesfoo.example.comandbar.example.combut notexample.com.- To match both a domain and all its subdomains, add two entries:
example.comand*.example.com.
CIDR range matching: write an entry as network/prefixLength to match every IP address in a range.
169.254.0.0/16blocks any address in the link-local range, regardless of scheme.http://10.0.0.0/8blocks HTTP requests to any RFC-191810.x.x.xaddress.- A CIDR entry matches only IP literals, never hostnames.
IP canonicalization: all textual representations of the same address match the same entry. 2852039166, 169.254.43518, and 169.254.169.254 are equivalent. No DNS lookup is performed during matching.
Underscore hostnames: hosts containing underscores (e.g. kestra_internal_db) are matched correctly.
When a URI is blocked, the task fails with an error that identifies the matching config key:
The URI http://169.254.169.254/... is in the configured denied list (kestra.tasks.http.denied-list).When a list is configured but the URI has no parseable host, the request is rejected rather than passing through silently.
This filter applies to HTTP plugin tasks and the http() Pebble expression function.
Encryption key
An encryption key under kestra.encryption.secret-key is required for SECRET inputs and outputs to be stored safely at rest. Without it, any flow that uses SECRET-typed inputs or outputs fails at runtime.
kestra: encryption: secret-key: BASE64_ENCODED_STRING_OF_32_CHARACTERSGenerate a key with:
openssl rand -base64 32See Encryption configuration for full details.
Plugin restrictions (EE)
Plugin restrictions control which task runners and plugins flow authors can use. At minimum, the Process task runner should be restricted in multi-tenant or untrusted environments — it executes directly on the worker host with no container isolation.
Configure plugin restrictions and worker isolation under Worker Isolation. For finer-grained policy enforcement across namespaces and tenants, use Policies to inject, validate, or reject plugin and flow configuration at save or execution time.
Plugin and code validation
- Plugin configuration: use Kestra’s plugin architecture, including Plugin Versioning, to control which plugins are allowed and which should be prohibited.
- CI/CD validation: add a Flow Validation step in your CI/CD pipeline to scan task definitions for disallowed patterns (e.g.,
169.254.169.254) and block merging if detected.
Management endpoint access
Kestra exposes internal endpoints on a separate management port (default 8081). These include health checks, metrics (/metrics), and runtime log level inspection (/loggers). This port is unauthenticated by default.
The primary protection is network isolation: do not expose port 8081 outside the internal network. Firewall or security-group rules should restrict access to the management port to trusted internal hosts only (monitoring agents, load balancer health checkers, operations tooling).
When network isolation alone is insufficient, Basic Auth can be added to the management port and the port can be moved from its default by configuring endpoints.all in your application.yml:
endpoints: all: port: 8084 # move away from the default 8081 basic-auth: username: <management-username> password: <strong-password>When basic-auth credentials are present, Kestra’s management endpoint filter requires Basic Auth on every request to that port.
See Kestra endpoints for the full list of what is exposed on the management port.
Webhook endpoints
Webhook URLs (/api/v1/{tenant}/executions/webhook/{namespace}/{flowId}/{key}) are served without authentication, even when Basic Authentication or SSO is enabled. The trigger’s key is the only credential, and anyone who has it can start the flow and choose the request body it receives.
- Treat the key as a credential: generate a long random value, store it as a secret, and reference it with
{{ secret('WEBHOOK_KEY') }}. To rotate it, update the secret; the flow does not change. - The key is part of the URL path, so it appears in reverse proxy, load balancer, and access logs. Restrict who can read those logs.
- Treat request data as untrusted input. Pass it to script tasks through
env, never insidecommands,beforeCommands,script, orinterpreter. See Pass request data to scripts through environment variables. - Run script tasks in webhook-triggered flows on the Docker or Kubernetes task runners rather than the Process runner. A command that runs through the Process runner runs on the worker host with the worker’s privileges.
- If your callers can authenticate (mTLS, IP ranges, or a provider signature such as HMAC), enforce it in a reverse proxy or API gateway in front of
/api/v1/*/executions/webhook/. The coreWebhooktrigger does not verify signatures. - Keep recursive rendering (
kestra.variables.recursive-rendering) disabled, which is the default. When enabled, expressions inside request data are evaluated.
ZIP bomb protection
Kestra guards against ZIP bomb attacks on the two endpoints that accept user-uploaded ZIP archives: flow import and namespace file upload. The protection is opt-in and disabled by default.
Enable it under kestra.security.zip-bomb-protection in your application.yml:
kestra: security: zip-bomb-protection: enabled: true max-number-of-entries: <integer> # maximum number of entries in the archive max-entry-size: <bytes> # maximum uncompressed size of a single entryBoth max-number-of-entries and max-entry-size are required when enabled: true. Set them based on the largest legitimate ZIP archives your users are expected to upload. When a ZIP exceeds either limit, the request is rejected with an HTTP 422 error.
The protection applies only to user-uploaded ZIPs (flow import and namespace file upload). Internal Kestra archives used for task execution caching are not affected.
Related configuration
- Encryption and secrets configuration — encryption key setup, secret backend configuration, and auth security settings.
- External Secrets Manager — integrate with AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault, and others to avoid storing credentials in Kestra directly.
- Policies — enforce governance rules that inject, validate, or reject plugin and flow configuration across namespaces and tenants.
Was this page helpful?