# How to Configure Retry Logic and Timeouts for gRPC Client Calls in Kratos Services

> Master gRPC client call retry logic and timeouts in Kratos services. Learn to use grpcWithTimeout, native retry policies, or custom interceptors for robust communication.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: how-to-guide
- Published: 2026-03-02

---

**To configure retry logic and timeouts for gRPC client calls in Kratos services, use `grpc.WithTimeout()` for per-request deadlines and either gRPC's native service-config retry policies via `grpc.WithOptions()` or custom unary interceptors via `grpc.WithUnaryInterceptor()`.**

The Kratos framework builds its gRPC client on top of the standard `google.golang.org/grpc` library, wrapping `grpc.Dial` with helper utilities located in the `transport/grpc` package. When you need to configure retry logic and timeouts for gRPC client calls, Kratos provides explicit options in [`transport/grpc/client.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client.go) while allowing direct access to underlying gRPC dial options for advanced scenarios.

## Configuring Request Timeouts in Kratos gRPC Clients

Kratos provides the `WithTimeout(d time.Duration)` option to enforce per-request deadlines. This option is defined in **[`transport/grpc/client.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client.go)** (lines 51–56) and is applied inside the `unaryClientInterceptor` function found in **[`transport/grpc/interceptor.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/interceptor.go)** (lines 25–30).

When a call exceeds the specified duration, the interceptor cancels the context with `context.DeadlineExceeded`. Note that this timeout applies **only to the RPC call**, not to the underlying connection establishment.

```go
import (
    "context"
    "time"
    
    "github.com/go-kratos/kratos/v2/transport/grpc"
    greeterpb "path/to/your/api/helloworld"
)

func NewGreeterClient() greeterpb.GreeterClient {
    // 5 seconds deadline for every RPC
    conn, _ := grpc.Dial(
        context.Background(),
        grpc.WithEndpoint("localhost:9000"),
        grpc.WithTimeout(5*time.Second),
    )
    return greeterpb.NewGreeterClient(conn)
}

```

The timeout is injected inside the unary interceptor via `ctx = context.WithTimeout(ctx, timeout)`, ensuring that any blocking RPC operation respects the deadline.

## Implementing Retry Logic for gRPC Calls

Kratos does not ship with a built-in retry interceptor for the client side. You have two idiomatic approaches to add retry capabilities: using gRPC's native service configuration or writing a custom unary interceptor.

### Option 1: Native gRPC Retry via Service Config

You can enable automatic retries by passing a JSON service configuration through `grpc.WithDefaultServiceConfig`, which Kratos forwards to the underlying dialer via the `WithOptions` wrapper (defined in [`transport/grpc/client.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client.go), lines 100–104).

```go
import (
    "context"
    
    "github.com/go-kratos/kratos/v2/transport/grpc"
    "google.golang.org/grpc"
    greeterpb "path/to/your/api/helloworld"
)

func NewGreeterClientWithRetry() greeterpb.GreeterClient {
    // JSON service config enabling 3 retries with exponential back-off
    const svcConfig = `{
        "methodConfig": [{
            "name": [{"service": "helloworld.Greeter"}],
            "retryPolicy": {
                "MaxAttempts": 4,
                "InitialBackoff": "0.1s",
                "MaxBackoff": "1s",
                "BackoffMultiplier": 2,
                "RetryableStatusCodes": ["UNAVAILABLE","RESOURCE_EXHAUSTED"]
            }
        }]
    }`

    conn, _ := grpc.Dial(
        context.Background(),
        grpc.WithEndpoint("localhost:9000"),
        grpc.WithOptions(
            grpc.WithDefaultServiceConfig(svcConfig),
        ),
    )
    return greeterpb.NewGreeterClient(conn)
}

```

This approach leverages the gRPC core's built-in retry mechanism, which interprets the policy when the server returns a retryable status code.

### Option 2: Custom Unary Interceptor

For custom logic, implement a `grpc.UnaryClientInterceptor` and register it using `WithUnaryInterceptor` (handled in [`transport/grpc/client.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client.go), lines 86–90). This interceptor runs **before** the built-in timeout interceptor, so the deadline still caps the total elapsed time across all retry attempts.

```go
import (
    "context"
    "time"
    
    "google.golang.org/grpc"
    "github.com/go-kratos/kratos/v2/transport/grpc"
    greeterpb "path/to/your/api/helloworld"
)

// Simple retry interceptor: up to 3 attempts, 200 ms pause
func retryUnaryInterceptor(maxAttempts int, backoff time.Duration) grpc.UnaryClientInterceptor {
    return func(
        ctx context.Context,
        method string,
        req, reply interface{},
        cc *grpc.ClientConn,
        invoker grpc.UnaryInvoker,
        opts ...grpc.CallOption,
    ) error {
        var err error
        for i := 0; i < maxAttempts; i++ {
            err = invoker(ctx, method, req, reply, cc, opts...)
            if err == nil {
                return nil
            }
            // Adapt to your own retryable error list
            time.Sleep(backoff)
        }
        return err
    }
}

func NewClientWithRetry() greeterpb.GreeterClient {
    conn, _ := grpc.Dial(
        context.Background(),
        grpc.WithEndpoint("localhost:9000"),
        grpc.WithUnaryInterceptor(
            retryUnaryInterceptor(3, 200*time.Millisecond),
        ),
        grpc.WithTimeout(5*time.Second),
    )
    return greeterpb.NewGreeterClient(conn)
}

```

## Combining Timeouts and Retries

You can safely combine per-call timeouts with retry policies to ensure that the total time spent on all retry attempts does not exceed your service-level objectives. The timeout interceptor wraps the entire invocation chain, including any retry logic you inject.

```go
conn, _ := grpc.Dial(
    context.Background(),
    grpc.WithEndpoint("localhost:9000"),
    grpc.WithTimeout(3*time.Second),                // Overall call deadline
    grpc.WithOptions(
        grpc.WithDefaultServiceConfig(svcConfig), // Native retry policy
    ),
)

```

In this configuration, the `unaryClientInterceptor` applies the deadline first, and the gRPC core handles the retry attempts within that bounded context.

## Summary

- **`WithTimeout`** sets per-RPC deadlines via the `unaryClientInterceptor` in [`transport/grpc/interceptor.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/interceptor.go).
- **`WithOptions`** passes raw `grpc.DialOption` values (like `WithDefaultServiceConfig`) directly to the underlying `grpc.DialContext`.
- **`WithUnaryInterceptor`** allows you to inject custom retry logic that executes before the timeout enforcement.
- Native gRPC retry policies defined in service config JSON are supported through the `WithOptions` wrapper.
- Reference implementations and unit tests are available in **[`transport/grpc/client_test.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client_test.go)**.

## Frequently Asked Questions

### How does Kratos apply the timeout to gRPC calls?

Kratos injects the timeout through the `unaryClientInterceptor` function in [`transport/grpc/interceptor.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/interceptor.go). This interceptor wraps the RPC invocation with `context.WithTimeout(ctx, timeout)` before delegating to the invoker, ensuring that any call exceeding the duration is cancelled with `context.DeadlineExceeded`.

### Can I use both native gRPC retry and a custom interceptor together?

Yes. You can pass `grpc.WithDefaultServiceConfig` via `WithOptions` for native retries while also supplying a custom interceptor via `WithUnaryInterceptor`. The custom interceptor executes first, followed by the timeout enforcement, while the native retry policy is handled by the gRPC core at the transport layer.

### Does the timeout include connection establishment time?

No. The `WithTimeout` option applies only to the RPC call itself, not to the underlying connection establishment. For connection timeouts, use `WithOptions` to pass `grpc.WithBlock()` and `grpc.WithTimeout()` dial options directly to the gRPC dialer.

### Where are the client options defined in the Kratos source code?

All client configuration options, including `WithTimeout`, `WithUnaryInterceptor`, and `WithOptions`, are defined in **[`transport/grpc/client.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client.go)**. The timeout enforcement logic resides in **[`transport/grpc/interceptor.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/interceptor.go)**, and usage examples are demonstrated in **[`transport/grpc/client_test.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/client_test.go)**.