# Pattern for Sharing Credentials and Socket Pools Across Multi-Region AWS SDK Clients

> Share AWS SDK credentials and socket pools across multi-region clients with a single CredentialProvider and NodeHttpHandler. Optimize performance and reduce overhead.

- Repository: [Amazon Web Services/agent-toolkit-for-aws](https://github.com/aws/agent-toolkit-for-aws)
- Tags: best-practices
- Published: 2026-07-03

---

**Create a single `CredentialProvider` and `NodeHttpHandler` in AWS SDK for JavaScript v3, then inject them into every regional client via the `credentials` and `requestHandler` configuration options to eliminate redundant credential resolutions and TCP socket pools.**

When applications communicate with AWS services across multiple regions, instantiating separate SDK clients for each region creates significant overhead. Each client typically resolves its own credentials and maintains isolated HTTP connection pools, leading to repeated filesystem reads, STS calls, and TLS handshakes. The `aws/agent-toolkit-for-aws` repository demonstrates patterns for sharing these heavy-weight resources across regional boundaries, optimizing both latency and resource utilization in multi-region architectures.

## Why Share Credentials and Socket Pools Across Regions?

Every AWS SDK v3 client initializes its own credential resolution chain and HTTP handler by default. This means:

- **Repeated credential resolution**: Each client independently reads from `~/.aws/credentials`, environment variables, or calls STS AssumeRole.
- **Isolated socket pools**: Each `NodeHttpHandler` creates separate `http.Agent` or `https.Agent` instances, preventing connection reuse across regions.
- **Memory overhead**: Multiple agents maintain duplicate sets of idle sockets.

By sharing a single `CredentialProvider` and `HttpHandler` across all clients, you centralize authentication logic and TCP connection pools while preserving the ability to target specific regional endpoints.

## The Core Implementation Pattern

The AWS SDK for JavaScript v3 uses dependency injection for its configuration. You can create reusable objects once and pass them to every client constructor.

### Create a Singleton Credential Provider

Import a credential provider from `@aws-sdk/credential-providers` and wrap it in a reusable function that returns `AwsCredentialIdentity`:

```typescript
import { fromIni, CredentialProvider, AwsCredentialIdentity } from "@aws-sdk/credential-providers";

const credentialProvider: CredentialProvider = async () => {
  const creds = await fromIni({ profile: "default" })();
  return creds as AwsCredentialIdentity;
};

```

This provider evaluates once and caches credentials for subsequent calls across all clients using it.

### Configure a Shared HTTP Handler with Keep-Alive

Instantiate `NodeHttpHandler` with optimized connection settings. This handler owns the underlying TCP sockets and enables keep-alive reuse across all regional clients:

```typescript
import { NodeHttpHandler } from "@aws-sdk/node-http-handler";

const httpHandler = new NodeHttpHandler({
  connectionTimeout: 3000,
  socketTimeout: 30000,
  // Optional: httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 50 })
});

```

### Instantiate Regional Clients with Shared Dependencies

Create a factory function that merges per-region configuration with the shared resources:

```typescript
import { S3Client, DynamoDBClient } from "@aws-sdk/client-s3"; // Replace with proper packages

function createClient<T extends { new (config: any): any }>(
  ClientCtor: T,
  region: string,
  extraConfig: Record<string, unknown> = {}
) {
  return new ClientCtor({
    region,
    credentials: credentialProvider,
    requestHandler: httpHandler,
    ...extraConfig,
  });
}

```

## Multi-Service, Multi-Region Implementation

Apply this pattern to instantiate clients for different services and regions while maintaining shared pools:

```typescript
// Initialize shared resources once per process
const usEast1S3 = createClient(S3Client, "us-east-1");
const euWest1Dynamo = createClient(DynamoDBClient, "eu-west-1");
const apSoutheast2Logs = createClient(CloudWatchLogsClient, "ap-southeast-2");

// Usage reuses the same TCP connections and credential cache
await usEast1S3.send(new PutObjectCommand({ Bucket: "bucket", Key: "key", Body: data }));
await euWest1Dynamo.send(new PutItemCommand({ TableName: "table", Item: item }));

```

## Optimization Strategies for Serverless Workloads

In Lambda or container environments, declare shared handlers outside the invocation handler to persist across warm starts:

```typescript
// Shared outside the handler function
const sharedS3 = createClient(S3Client, "us-east-1");
const sharedDynamo = createClient(DynamoDBClient, "eu-west-1");

export const handler = async () => {
  // Reuses existing connections from previous invocations
  await sharedS3.send(new ListBucketsCommand({}));
  await sharedDynamo.send(new ListTablesCommand({}));
};

```

This reduces socket accumulation and keeps you within default `maxSockets` limits.

## Custom Socket Pool Configuration

For high-throughput scenarios, customize the underlying agent:

```typescript
import https from "https";

const customAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 100,
  keepAliveMsecs: 3000
});

const httpHandler = new NodeHttpHandler({ httpsAgent: customAgent });

const highThroughputS3 = createClient(S3Client, "eu-central-1", { 
  requestHandler: httpHandler 
});

```

## Key Files in the Agent Toolkit

The `aws/agent-toolkit-for-aws` repository contains reference implementations demonstrating these patterns:

- **[`skills/core-skills/aws-observability/assets/alarm-template.ts`](https://github.com/aws/agent-toolkit-for-aws/blob/main/skills/core-skills/aws-observability/assets/alarm-template.ts)**: Demonstrates the singleton export pattern for reusable configuration objects.
- **[`plugins/aws-core/skills/aws-serverless/SKILL.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-serverless/SKILL.md)**: Documents how the toolkit expects MCP-configured clients that follow shared resource patterns.
- **[`README.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/README.md)**: Provides the architectural layout for implementing custom TypeScript utilities that leverage shared SDK configurations.

## Summary

- **Share the `CredentialProvider`**: Create one credential resolver and pass it to every client to avoid redundant filesystem and STS calls.
- **Share the `HttpHandler`**: Use a single `NodeHttpHandler` (or `FetchHttpHandler` in browsers) to maintain one TCP socket pool across all regions.
- **Use a factory function**: Wrap client instantiation to consistently merge per-region settings with shared dependencies.
- **Initialize once**: In serverless environments, declare shared clients outside the handler to persist across invocations.
- **Customize carefully**: Override `maxSockets` and keep-alive settings at the handler level when tuning for specific throughput requirements.

## Frequently Asked Questions

### Can I share credentials across different AWS accounts?

Yes. The shared `CredentialProvider` can return credentials for any account, including those from STS AssumeRole operations. Configure the provider to assume different roles based on the service or region requirements, or use a master credential provider that handles multi-account logic internally. All clients using the same provider instance will benefit from credential caching.

### Does this pattern work in browser environments?

Absolutely. Replace `NodeHttpHandler` with `FetchHttpHandler` from `@aws-sdk/fetch-http-handler`. The browser automatically manages connection pooling through the Fetch API, but sharing the handler instance still ensures consistent timeout and request configuration across all service clients.

### How do I handle per-region configuration overrides?

Pass region-specific options through the `extraConfig` parameter in your factory function. The SDK merges these with the shared configuration, allowing you to customize retry strategies, custom endpoints, or request handlers for specific regions while maintaining shared credentials and socket pools for the rest.

### What happens if I mutate the shared handler after creating clients?

**Never mutate shared handlers or credential providers after initialization.** The SDK expects these objects to remain immutable for the client's lifetime. Modifying `maxSockets` or credential logic after instantiation can lead to race conditions, connection leaks, or authentication failures. Always create new handler instances if you need different configuration profiles.