Skip to main content
Temporal Server provides comprehensive security features including mutual TLS, JWT authentication, and role-based authorization.

Transport Layer Security (TLS)

Server TLS Configuration

Configure TLS for each service group:
global:
  tls:
    # Backend service communication (history, matching)
    internode:
      server:
        certFile: "/path/to/internode-cert.pem"
        keyFile: "/path/to/internode-key.pem"
        clientCAFiles:
          - "/path/to/ca-cert.pem"
        requireClientAuth: true
      client:
        serverName: "temporal-internode"
        rootCAFiles:
          - "/path/to/ca-cert.pem"
        disableHostVerification: false
    
    # Frontend service
    frontend:
      server:
        certFile: "/path/to/frontend-cert.pem"
        keyFile: "/path/to/frontend-key.pem"
        clientCAFiles:
          - "/path/to/ca-cert.pem"
        requireClientAuth: true
      client:
        serverName: "temporal-frontend"
        rootCAFiles:
          - "/path/to/ca-cert.pem"

TLS Options

Server Configuration:
  • certFile - Server certificate (PEM format)
  • keyFile - Private key (PEM format)
  • certData - Base64 encoded certificate (alternative to file)
  • keyData - Base64 encoded key (alternative to file)
  • clientCAFiles - CA certificates for client verification
  • clientCAData - Base64 encoded CA certificates
  • requireClientAuth - Enable mutual TLS (mTLS)
Client Configuration:
  • serverName - Expected server name in certificate (for SNI)
  • rootCAFiles - CA certificates to verify server
  • rootCAData - Base64 encoded CA certificates
  • disableHostVerification - Skip hostname verification (not recommended)
  • forceTLS - Use TLS even without client certificates

Per-Host TLS Overrides

Different TLS settings for different hostnames:
global:
  tls:
    frontend:
      server:
        certFile: "/path/to/default-cert.pem"
        keyFile: "/path/to/default-key.pem"
      hostOverrides:
        "temporal.example.com":
          certFile: "/path/to/example-cert.pem"
          keyFile: "/path/to/example-key.pem"
          requireClientAuth: true
        "temporal.internal.local":
          certFile: "/path/to/internal-cert.pem"
          keyFile: "/path/to/internal-key.pem"
          requireClientAuth: false

System Worker TLS

Configure TLS for system workers:
global:
  tls:
    systemWorker:
      certFile: "/path/to/worker-cert.pem"
      keyFile: "/path/to/worker-key.pem"
      client:
        serverName: "temporal-frontend"
        rootCAFiles:
          - "/path/to/ca-cert.pem"

Remote Cluster TLS

For cross-cluster replication:
global:
  tls:
    remoteClusters:
      cluster-east:
        client:
          serverName: "temporal-east.example.com"
          rootCAFiles:
            - "/path/to/east-ca.pem"
      cluster-west:
        client:
          serverName: "temporal-west.example.com"
          rootCAFiles:
            - "/path/to/west-ca.pem"

Certificate Expiration Monitoring

global:
  tls:
    expirationChecks:
      warningWindow: "720h"  # 30 days - log warnings
      errorWindow: "168h"    # 7 days - log errors
      checkInterval: "1h"    # Check every hour
    refreshInterval: "24h"  # Reload certs from disk every 24h
Monitor certificate expiration with metrics:
  • certificates_expired - Number of expired certificates
  • certificates_expiring - Certificates expiring within warning window

Authentication

JWT Authentication

Temporal supports JWT (JSON Web Token) authentication:
global:
  authorization:
    jwtKeyProvider:
      keySourceURIs:
        - "file:///path/to/public-key.pem"
        - "https://auth.example.com/.well-known/jwks.json"
      refreshInterval: "1h"

Custom Claim Mapper

Implement the ClaimMapper interface to extract claims from auth tokens:
type ClaimMapper interface {
    GetClaims(authInfo *AuthInfo) (*Claims, error)
}
AuthInfo Structure:
type AuthInfo struct {
    AuthToken     string           // JWT or other token
    TLSSubject    *pkix.Name       // Client certificate subject
    TLSConnection *credentials.TLSInfo
    ExtraData     string           // Additional auth data
    Audience      string           // Target audience
}
Default JWT Claim Mapper: The default implementation (DefaultJWTClaimMapper) extracts:
  • sub - Subject identifier
  • aud - Audience validation
  • exp - Token expiration
  • iat - Issued at time
  • System and namespace roles from custom claims

Authorization Headers

Configure custom header names:
global:
  authorization:
    authHeaderName: "authorization"        # JWT token header
    authExtraHeaderName: "authorization-extras"  # Additional auth data

Authorization

Role-Based Access Control

Temporal implements role-based authorization with three permission levels: System-Level Roles:
  • Admin - Full system access, all operations
  • Writer - Non-admin write operations
  • Reader - Read-only operations
Namespace-Level Roles:
  • Admin - Full namespace access
  • Writer - Non-admin operations in namespace
  • Reader - Read-only operations in namespace

Authorization Configuration

global:
  authorization:
    enabled: true
    claimMapper: "default"  # or custom implementation
    authorizer: "default"   # or custom implementation

Default Authorization Rules

The DefaultAuthorizer implements these rules:
  1. Health check APIs - Always allowed (no auth required)
  2. System Admin - Access all APIs, all namespaces
  3. System Writer - Non-admin APIs, all namespaces
  4. System Reader - Read-only APIs, all namespaces
  5. Namespace Admin - All APIs in their namespaces
  6. Namespace Writer - Non-admin APIs in their namespaces
  7. Namespace Reader - Read-only APIs in their namespaces

API Access Levels

Cluster-Scoped APIs:
  • Require system-level roles
  • Examples: namespace management, cluster operations
Namespace-Scoped APIs:
  • Require namespace-level or system-level roles
  • Examples: workflow operations, visibility queries

Claims Structure

type Claims struct {
    System     Role                // System-level role
    Namespaces map[string]Role     // Namespace roles
    Extensions any                 // Custom extensions
}
Role Values:
const (
    RoleUndefined Role = 0
    RoleReader    Role = 1
    RoleWriter    Role = 2
    RoleAdmin     Role = 4
)

Custom Authorizer

Implement the Authorizer interface for custom logic:
type Authorizer interface {
    Authorize(ctx context.Context, claims *Claims, target *CallTarget) (Result, error)
}

type CallTarget struct {
    Namespace string  // Target namespace
    APIName   string  // API method name
    Request   any     // Full request object
}

type Result struct {
    Decision Decision  // Allow or Deny
    Reason   string    // Optional reason for denial
}

Cross-Namespace Authorization

For workflows that interact across namespaces:
global:
  dynamicConfig:
    system.enableCrossNamespaceCommands:
      - value: true
        constraints: {}
When enabled, the system authorizes:
  • SignalExternalWorkflowExecution to different namespace
  • StartChildWorkflowExecution in different namespace
  • RequestCancelExternalWorkflowExecution for different namespace

Authorization Metrics

Monitor authorization:
service_authorization_latency      # Authorization check duration
service_errors_unauthorized        # Denied requests
service_errors_authorize_failed    # Authorization errors

Expose Authorization Errors

global:
  dynamicConfig:
    system.exposeAuthorizerErrors:
      - value: false  # Return generic error
        constraints: {}
When false, returns generic “Request unauthorized” message. When true, includes specific authorization failure details.

Database Encryption

At-Rest Encryption

Configure encryption at the database level:
persistence:
  datastores:
    default:
      cassandra:
        hosts: "cassandra.example.com"
        keyspace: "temporal_encrypted"
        tls:
          enabled: true
          certFile: "/path/to/db-cert.pem"
          keyFile: "/path/to/db-key.pem"
          caFile: "/path/to/db-ca.pem"
          enableHostVerification: true
          serverName: "cassandra.example.com"

Payload Encryption

Implement application-level encryption using data converters in SDK clients. Temporal Server stores payloads as opaque bytes.

Security Best Practices

1. Enable mTLS Everywhere

global:
  tls:
    internode:
      server:
        requireClientAuth: true  # Enforce mTLS
    frontend:
      server:
        requireClientAuth: true

2. Rotate Certificates Regularly

  • Set refreshInterval to reload certificates
  • Monitor expiration metrics
  • Automate certificate renewal (e.g., cert-manager)

3. Use Strong JWT Algorithms

Supported algorithms:
  • RS256, RS384, RS512 (RSA signatures)
  • ES256, ES384, ES512 (ECDSA signatures)
  • PS256, PS384, PS512 (RSA-PSS signatures)
Avoid:
  • HS256 (symmetric, shared secrets)
  • None algorithm

4. Implement Least Privilege

  • Grant minimum required roles
  • Use namespace-scoped roles over system roles
  • Separate admin operations to dedicated accounts

5. Network Segmentation

  • Isolate internode traffic on private network
  • Expose frontend only through load balancer
  • Use firewall rules to restrict access

6. Audit Logging

Enable structured logging to capture:
  • Authorization decisions
  • Failed authentication attempts
  • Admin operations
log:
  level: "info"
  encoding: "json"
  outputFile: "/var/log/temporal/audit.log"

7. Rate Limiting

Configure via dynamic config:
dynamicConfigClient:
  filepath: "config/dynamicconfig/production.yaml"
# production.yaml
frontend.rps:
  - value: 2400
    constraints: {}
frontend.namespaceRPS:
  - value: 1200
    constraints:
      namespace: "production-namespace"

8. Secure Database Access

  • Use dedicated database credentials per Temporal service
  • Enable database audit logging
  • Restrict database network access
  • Use encrypted connections
  • Regularly rotate database passwords

9. Secrets Management

Store secrets externally:
  • Kubernetes Secrets
  • HashiCorp Vault
  • AWS Secrets Manager
  • Azure Key Vault
Mount secrets as files, reference in config:
global:
  tls:
    frontend:
      server:
        certFile: "/var/run/secrets/tls/cert.pem"
        keyFile: "/var/run/secrets/tls/key.pem"

Troubleshooting

TLS Handshake Failures

Symptoms:
  • x509: certificate signed by unknown authority
  • x509: certificate is valid for X, not Y
  • tls: bad certificate
Solutions:
  1. Verify CA certificate chain
  2. Check serverName matches certificate CN/SAN
  3. Ensure requireClientAuth matches client configuration
  4. Validate certificate expiration dates

Authorization Denied

Symptoms:
  • Request unauthorized
  • service_errors_unauthorized metrics increasing
Solutions:
  1. Check JWT signature and expiration
  2. Verify audience claim matches expected value
  3. Review role mappings in claims
  4. Enable exposeAuthorizerErrors for debugging (development only)
  5. Check authorization metrics with namespace tags

Certificate Rotation

Temporal reloads certificates based on refreshInterval. To force reload, restart the service.

See Also

Build docs developers (and LLMs) love