Pattern for Sharing Credentials and Socket Pools Across Multi-Region AWS SDK Clients
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
NodeHttpHandlercreates separatehttp.Agentorhttps.Agentinstances, 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:
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:
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:
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:
// 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:
// 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:
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: Demonstrates the singleton export pattern for reusable configuration objects.plugins/aws-core/skills/aws-serverless/SKILL.md: Documents how the toolkit expects MCP-configured clients that follow shared resource patterns.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 singleNodeHttpHandler(orFetchHttpHandlerin 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
maxSocketsand 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →