> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/temporalio/temporal/llms.txt
> Use this file to discover all available pages before exploring further.

# Server Configuration

> Configure Temporal Server with YAML configuration files

Temporal Server uses YAML configuration files to define persistence, networking, security, and operational settings. This guide covers all configuration options with real examples.

## Configuration File Structure

A complete configuration file has these top-level sections:

```yaml theme={null}
log:                    # Logging configuration
persistence:           # Database and storage settings
global:                # Global settings (TLS, metrics, membership)
services:              # Individual service configuration
clusterMetadata:       # Cluster identity and failover settings
dcRedirectionPolicy:   # Cross-datacenter request routing
archival:              # Workflow history archival
namespaceDefaults:     # Default namespace settings
publicClient:          # Client configuration for workers
dynamicConfigClient:   # Dynamic configuration settings
```

## Logging Configuration

Configure log output format and level:

```yaml theme={null}
log:
  stdout: true
  level: info  # debug, info, warn, error
```

<ParamField path="stdout" type="boolean" default="true">
  Output logs to stdout instead of files
</ParamField>

<ParamField path="level" type="string" default="info">
  Minimum log level: `debug`, `info`, `warn`, `error`
</ParamField>

## Persistence Configuration

### Basic Setup

```yaml theme={null}
persistence:
  numHistoryShards: 4
  defaultStore: default
  visibilityStore: visibility
  datastores:
    default:
      # Database configuration
    visibility:
      # Visibility database configuration
```

<ParamField path="numHistoryShards" type="integer" default="4">
  Number of history shards. Higher values improve scalability.

  **Recommendations**:

  * Development: 4
  * Small production: 128-512
  * Large production: 1024-4096

  <Warning>Cannot be changed after initial deployment</Warning>
</ParamField>

### MySQL Configuration

<CodeGroup>
  ```yaml Complete Example theme={null}
  persistence:
    numHistoryShards: 4
    defaultStore: mysql-default
    visibilityStore: mysql-visibility
    datastores:
      mysql-default:
        sql:
          pluginName: "mysql8"
          databaseName: "temporal"
          connectAddr: "127.0.0.1:3306"
          connectProtocol: "tcp"
          user: "temporal"
          password: "temporal"
          maxConns: 20
          maxIdleConns: 20
          maxConnLifetime: "1h"
          tls:
            enabled: false
            caFile: ""
            certFile: ""
            keyFile: ""
            enableHostVerification: false
            serverName: ""
      mysql-visibility:
        sql:
          pluginName: "mysql8"
          databaseName: "temporal_visibility"
          connectAddr: "127.0.0.1:3306"
          connectProtocol: "tcp"
          user: "temporal"
          password: "temporal"
          maxConns: 2
          maxIdleConns: 2
          maxConnLifetime: "1h"
  ```

  ```yaml With TLS theme={null}
  mysql-default:
    sql:
      pluginName: "mysql8"
      databaseName: "temporal"
      connectAddr: "mysql.example.com:3306"
      connectProtocol: "tcp"
      user: "temporal"
      password: "${MYSQL_PASSWORD}"
      maxConns: 20
      maxIdleConns: 20
      maxConnLifetime: "1h"
      tls:
        enabled: true
        caFile: "/etc/temporal/certs/ca.pem"
        certFile: "/etc/temporal/certs/client-cert.pem"
        keyFile: "/etc/temporal/certs/client-key.pem"
        enableHostVerification: true
        serverName: "mysql.example.com"
  ```
</CodeGroup>

### PostgreSQL Configuration

```yaml theme={null}
persistence:
  numHistoryShards: 4
  defaultStore: postgres-default
  visibilityStore: postgres-visibility
  datastores:
    postgres-default:
      sql:
        pluginName: "postgres12"  # or "postgres12_pgx"
        databaseName: "temporal"
        connectAddr: "127.0.0.1:5432"
        connectProtocol: "tcp"
        user: "temporal"
        password: "temporal"
        maxConns: 20
        maxIdleConns: 20
        maxConnLifetime: "1h"
        tls:
          enabled: false
    postgres-visibility:
      sql:
        pluginName: "postgres12"
        databaseName: "temporal_visibility"
        connectAddr: "127.0.0.1:5432"
        connectProtocol: "tcp"
        user: "temporal"
        password: "temporal"
        maxConns: 2
        maxIdleConns: 2
        maxConnLifetime: "1h"
```

<Note>
  Use `postgres12_pgx` for better performance with the pgx driver.
</Note>

### Cassandra Configuration

```yaml theme={null}
persistence:
  numHistoryShards: 4
  defaultStore: cass-default
  visibilityStore: es-visibility  # Cassandra not supported for visibility
  datastores:
    cass-default:
      cassandra:
        hosts: "127.0.0.1"
        keyspace: "temporal"
        port: 9042
        user: ""
        password: ""
        maxConns: 20
        tls:
          enabled: false
          caFile: ""
          certFile: ""
          keyFile: ""
          enableHostVerification: false
          serverName: ""
```

<Warning>
  Cassandra is no longer supported for visibility stores. Use MySQL, PostgreSQL, or Elasticsearch.
</Warning>

### Elasticsearch Configuration

For advanced visibility features:

```yaml theme={null}
persistence:
  visibilityStore: es-visibility
  datastores:
    es-visibility:
      elasticsearch:
        version: "v7"  # v7 or v8
        logLevel: "error"
        url:
          scheme: "http"  # or "https"
          host: "127.0.0.1:9200"
        username: ""
        password: ""
        indices:
          visibility: "temporal_visibility_v1_dev"
          # Optional secondary index for dual visibility
          secondary_visibility: "temporal_visibility_v2_dev"
        closeIdleConnectionsInterval: "15s"
```

### Connection Pool Settings

<ParamField path="maxConns" type="integer" default="20">
  Maximum number of connections to the database
</ParamField>

<ParamField path="maxIdleConns" type="integer" default="20">
  Maximum number of idle connections to maintain
</ParamField>

<ParamField path="maxConnLifetime" type="duration" default="1h">
  Maximum lifetime of a connection before it is closed
</ParamField>

**Tuning Guidelines**:

```yaml theme={null}
# High throughput workloads
maxConns: 50
maxIdleConns: 25
maxConnLifetime: "30m"

# Resource-constrained environments
maxConns: 10
maxIdleConns: 5
maxConnLifetime: "2h"
```

## Global Configuration

### Membership Settings

```yaml theme={null}
global:
  membership:
    maxJoinDuration: 30s
    broadcastAddress: "127.0.0.1"
```

<ParamField path="maxJoinDuration" type="duration" default="30s">
  Maximum time for a service to join the cluster ring
</ParamField>

<ParamField path="broadcastAddress" type="string">
  Address advertised to other cluster members. Leave empty for auto-detection.
</ParamField>

### Metrics Configuration

<Tabs>
  <Tab title="Prometheus">
    ```yaml theme={null}
    global:
      metrics:
        prometheus:
          framework: "opentelemetry"  # or "tally"
          timerType: "histogram"      # or "summary"
          listenAddress: "127.0.0.1:8000"
    ```

    Access metrics at `http://localhost:8000/metrics`
  </Tab>

  <Tab title="StatsD">
    ```yaml theme={null}
    global:
      metrics:
        statsd:
          hostPort: "127.0.0.1:8125"
          prefix: "temporal"
    ```

    Metrics are pushed to StatsD daemon
  </Tab>
</Tabs>

### Profiling Configuration

```yaml theme={null}
global:
  pprof:
    port: 7936  # Set to 0 to disable
```

Access profiling data:

* `http://localhost:7936/debug/pprof/heap`
* `http://localhost:7936/debug/pprof/profile`
* `http://localhost:7936/debug/pprof/goroutine`

### TLS Configuration

<Tabs>
  <Tab title="Internode TLS">
    ```yaml theme={null}
    global:
      tls:
        refreshInterval: "0s"  # Auto-refresh certificates
        internode:
          server:
            requireClientAuth: true
            certFile: "/certs/server-cert.pem"
            keyFile: "/certs/server-key.pem"
            clientCaFiles:
              - "/certs/ca-cert.pem"
          client:
            serverName: "temporal-cluster"
            disableHostVerification: false
            rootCaFiles:
              - "/certs/ca-cert.pem"
    ```

    Secures communication between Temporal services (Frontend, History, Matching, Worker)
  </Tab>

  <Tab title="Frontend TLS">
    ```yaml theme={null}
    global:
      tls:
        frontend:
          server:
            requireClientAuth: true
            certFile: "/certs/frontend-cert.pem"
            keyFile: "/certs/frontend-key.pem"
            clientCaFiles:
              - "/certs/client-ca-cert.pem"
          client:
            serverName: "temporal.example.com"
            disableHostVerification: false
            rootCaFiles:
              - "/certs/ca-cert.pem"
    ```

    Secures communication between clients and Frontend service
  </Tab>

  <Tab title="Certificate Rotation">
    ```yaml theme={null}
    global:
      tls:
        refreshInterval: "1h"  # Check for cert changes hourly
        expirationChecks:
          warningWindow: "720h"  # 30 days
          errorWindow: "168h"    # 7 days
          checkInterval: "24h"   # Check daily
    ```

    Automatically reload certificates and warn before expiration
  </Tab>
</Tabs>

### Authorization Configuration

```yaml theme={null}
global:
  authorization:
    jwtKeyProvider:
      keySourceURIs:
        - "https://auth.example.com/.well-known/jwks.json"
        - "file:///etc/temporal/certs/public-key.pem"
      refreshInterval: "1m"
    permissionsClaimName: "permissions"
    permissionsRegex: ""
    authorizer: ""  # Custom authorizer plugin
    claimMapper: ""  # Custom claim mapper plugin
```

## Service Configuration

### Frontend Service

```yaml theme={null}
services:
  frontend:
    rpc:
      grpcPort: 7233
      membershipPort: 6933
      bindOnIP: "0.0.0.0"
      httpPort: 7243
```

### History Service

```yaml theme={null}
services:
  history:
    rpc:
      grpcPort: 7234
      membershipPort: 6934
      bindOnIP: "0.0.0.0"
```

### Matching Service

```yaml theme={null}
services:
  matching:
    rpc:
      grpcPort: 7235
      membershipPort: 6935
      bindOnIP: "0.0.0.0"
```

### Worker Service

```yaml theme={null}
services:
  worker:
    rpc:
      grpcPort: 7239
      membershipPort: 6939
      bindOnIP: "0.0.0.0"
```

### Internal Frontend Service

```yaml theme={null}
services:
  internal-frontend:
    rpc:
      grpcPort: 7236
      membershipPort: 6936
      bindOnIP: "0.0.0.0"
      httpPort: 7246
```

<Note>
  The internal-frontend service is optional and used for internal cross-cluster communication.
</Note>

## Cluster Metadata

### Single Cluster

```yaml theme={null}
clusterMetadata:
  enableGlobalNamespace: false
  failoverVersionIncrement: 10
  masterClusterName: "active"
  currentClusterName: "active"
  clusterInformation:
    active:
      enabled: true
      initialFailoverVersion: 1
      rpcName: "frontend"
      rpcAddress: "localhost:7233"
      httpAddress: "localhost:7243"

dcRedirectionPolicy:
  policy: "noop"
```

### Multi-Cluster Setup

```yaml theme={null}
clusterMetadata:
  enableGlobalNamespace: true
  failoverVersionIncrement: 100
  masterClusterName: "cluster-a"
  currentClusterName: "cluster-a"
  clusterInformation:
    cluster-a:
      enabled: true
      initialFailoverVersion: 1
      rpcName: "frontend"
      rpcAddress: "cluster-a.example.com:7233"
      httpAddress: "cluster-a.example.com:7243"
    cluster-b:
      enabled: true
      initialFailoverVersion: 2
      rpcName: "frontend"
      rpcAddress: "cluster-b.example.com:7233"
      httpAddress: "cluster-b.example.com:7243"

dcRedirectionPolicy:
  policy: "all-apis-forwarding"
```

<ParamField path="enableGlobalNamespace" type="boolean" default="false">
  Enable global namespaces for multi-cluster replication
</ParamField>

<ParamField path="failoverVersionIncrement" type="integer" default="10">
  Version increment between clusters. Use larger values (100+) for multi-cluster.
</ParamField>

## Archival Configuration

Archive completed workflow histories for long-term storage:

<Tabs>
  <Tab title="File Store">
    ```yaml theme={null}
    archival:
      history:
        state: "enabled"    # enabled or disabled
        enableRead: true
        provider:
          filestore:
            fileMode: "0666"
            dirMode: "0766"
      visibility:
        state: "enabled"
        enableRead: true
        provider:
          filestore:
            fileMode: "0666"
            dirMode: "0766"

    namespaceDefaults:
      archival:
        history:
          state: "disabled"  # Disabled by default
          URI: "file:///mnt/temporal/archival"
        visibility:
          state: "disabled"
          URI: "file:///mnt/temporal/vis-archival"
    ```
  </Tab>

  <Tab title="S3">
    ```yaml theme={null}
    archival:
      history:
        state: "enabled"
        enableRead: true
        provider:
          s3store:
            region: "us-east-1"
            endpoint: ""
            s3ForcePathStyle: false
      visibility:
        state: "enabled"
        enableRead: true
        provider:
          s3store:
            region: "us-east-1"

    namespaceDefaults:
      archival:
        history:
          state: "disabled"
          URI: "s3://my-bucket/temporal/archival"
        visibility:
          state: "disabled"
          URI: "s3://my-bucket/temporal/vis-archival"
    ```
  </Tab>

  <Tab title="Google Cloud Storage">
    ```yaml theme={null}
    archival:
      history:
        state: "enabled"
        enableRead: true
        provider:
          gstorage:
            credentialsPath: "/etc/temporal/gcp-credentials.json"
      visibility:
        state: "enabled"
        enableRead: true
        provider:
          gstorage:
            credentialsPath: "/etc/temporal/gcp-credentials.json"

    namespaceDefaults:
      archival:
        history:
          state: "disabled"
          URI: "gs://my-bucket/temporal/archival"
        visibility:
          state: "disabled"
          URI: "gs://my-bucket/temporal/vis-archival"
    ```
  </Tab>
</Tabs>

## Dynamic Configuration

```yaml theme={null}
dynamicConfigClient:
  filepath: "/etc/temporal/config/dynamicconfig/docker.yaml"
  pollInterval: "60s"
```

Dynamic configuration allows runtime changes without server restart. Example dynamic config file:

```yaml dynamicconfig/production.yaml theme={null}
# Limit workflow execution history size
history.maxAutoResetPoints:
  - value: 20
    constraints:
      namespace: "my-namespace"

# Configure retention period
system.defaultWorkflowExecutionRetentionPeriod:
  - value: "30d"

# Set task queue limits
matching.numTaskqueueWritePartitions:
  - value: 4
    constraints:
      namespace: "high-throughput-namespace"
```

## Environment Variable Templating

Configuration files support Go template syntax for environment variables:

```yaml theme={null}
persistence:
  datastores:
    default:
      sql:
        connectAddr: "{{ env \"MYSQL_HOST\" }}:{{ default \"3306\" (env \"MYSQL_PORT\") }}"
        user: "{{ env \"MYSQL_USER\" }}"
        password: "{{ env \"MYSQL_PASSWORD\" }}"
```

Template functions:

* `env "VAR"` - Get environment variable
* `default "value" (env "VAR")` - Get with default
* `coalesce (env "VAR1") (env "VAR2") "default"` - First non-empty value

## Complete Example

<Accordion title="View complete production configuration">
  ```yaml config/production.yaml theme={null}
  log:
    stdout: true
    level: info

  persistence:
    numHistoryShards: 512
    defaultStore: mysql-default
    visibilityStore: es-visibility
    datastores:
      mysql-default:
        sql:
          pluginName: "mysql8"
          databaseName: "temporal"
          connectAddr: "mysql.example.com:3306"
          connectProtocol: "tcp"
          user: "temporal"
          password: "${MYSQL_PASSWORD}"
          maxConns: 50
          maxIdleConns: 25
          maxConnLifetime: "30m"
          tls:
            enabled: true
            caFile: "/etc/temporal/certs/mysql-ca.pem"
            enableHostVerification: true
            serverName: "mysql.example.com"
      es-visibility:
        elasticsearch:
          version: "v7"
          logLevel: "error"
          url:
            scheme: "https"
            host: "elasticsearch.example.com:9200"
          username: "elastic"
          password: "${ES_PASSWORD}"
          indices:
            visibility: "temporal_visibility_v1_prod"
          closeIdleConnectionsInterval: "15s"

  global:
    membership:
      maxJoinDuration: 30s
      broadcastAddress: ""
    pprof:
      port: 0
    metrics:
      prometheus:
        framework: "opentelemetry"
        timerType: "histogram"
        listenAddress: "0.0.0.0:8000"
    tls:
      refreshInterval: "1h"
      expirationChecks:
        warningWindow: "720h"
        errorWindow: "168h"
        checkInterval: "24h"
      internode:
        server:
          requireClientAuth: true
          certFile: "/etc/temporal/certs/server-cert.pem"
          keyFile: "/etc/temporal/certs/server-key.pem"
          clientCaFiles:
            - "/etc/temporal/certs/ca-cert.pem"
        client:
          serverName: "temporal-server"
          disableHostVerification: false
          rootCaFiles:
            - "/etc/temporal/certs/ca-cert.pem"
      frontend:
        server:
          requireClientAuth: true
          certFile: "/etc/temporal/certs/frontend-cert.pem"
          keyFile: "/etc/temporal/certs/frontend-key.pem"
          clientCaFiles:
            - "/etc/temporal/certs/client-ca-cert.pem"
        client:
          serverName: "temporal.example.com"
          disableHostVerification: false
          rootCaFiles:
            - "/etc/temporal/certs/ca-cert.pem"
    authorization:
      jwtKeyProvider:
        keySourceURIs:
          - "https://auth.example.com/.well-known/jwks.json"
        refreshInterval: "1m"
      permissionsClaimName: "permissions"

  services:
    frontend:
      rpc:
        grpcPort: 7233
        membershipPort: 6933
        bindOnIP: "0.0.0.0"
        httpPort: 7243
    history:
      rpc:
        grpcPort: 7234
        membershipPort: 6934
        bindOnIP: "0.0.0.0"
    matching:
      rpc:
        grpcPort: 7235
        membershipPort: 6935
        bindOnIP: "0.0.0.0"
    worker:
      rpc:
        grpcPort: 7239
        membershipPort: 6939
        bindOnIP: "0.0.0.0"

  clusterMetadata:
    enableGlobalNamespace: false
    failoverVersionIncrement: 10
    masterClusterName: "active"
    currentClusterName: "active"
    clusterInformation:
      active:
        enabled: true
        initialFailoverVersion: 1
        rpcName: "frontend"
        rpcAddress: "temporal.example.com:7233"
        httpAddress: "temporal.example.com:7243"

  dcRedirectionPolicy:
    policy: "noop"

  archival:
    history:
      state: "enabled"
      enableRead: true
      provider:
        s3store:
          region: "us-east-1"
    visibility:
      state: "enabled"
      enableRead: true
      provider:
        s3store:
          region: "us-east-1"

  namespaceDefaults:
    archival:
      history:
        state: "disabled"
        URI: "s3://temporal-archival-prod/history"
      visibility:
        state: "disabled"
        URI: "s3://temporal-archival-prod/visibility"

  dynamicConfigClient:
    filepath: "/etc/temporal/config/dynamicconfig/production.yaml"
    pollInterval: "60s"
  ```
</Accordion>

## Validation

Validate your configuration before deployment:

```bash theme={null}
# Dry run to check configuration
temporal-server --config /etc/temporal/config/production.yaml start --dry-run

# Check for common issues
temporal-server --config /etc/temporal/config/production.yaml validate
```
