The Error
Your app is running inside a private VPC subnet. It calls AWS Secrets Manager โ and gets slapped with this:
EndpointConnectionError: Could not connect to the endpoint URL: https://secretsmanager.us-east-1.amazonaws.com
ConnectionError: HTTPSConnectionPool(host='secretsmanager.us-east-1.amazonaws.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object>: Failed to establish a new connection: [Errno -2] Name or service not known'))
Shows up in Lambda logs, on an EC2 instance in a private subnet, inside an ECS task โ anywhere with no path to the public internet.
Why This Happens
AWS SDK targets secretsmanager.<region>.amazonaws.com by default โ a public endpoint. Private subnets without a NAT Gateway or Internet Gateway have no route there. DNS either fails to resolve the name, or the TCP handshake simply never completes.
The solution: a VPC Interface Endpoint (AWS PrivateLink) for Secrets Manager. It injects a private IP โ something like 10.0.1.45 โ into your VPC's DNS. After that, secretsmanager.us-east-1.amazonaws.com resolves to an address inside your VPC. No internet involved.
Step-by-Step Fix
Step 1 โ Confirm the root cause
SSH into the affected instance (or use ECS Exec / Lambda test) and probe the endpoint directly:
# Can you reach the public endpoint at all?
curl -v https://secretsmanager.us-east-1.amazonaws.com
# Check DNS resolution
nslookup secretsmanager.us-east-1.amazonaws.com
Could not resolve host or a hanging connection confirms it: the VPC has no route to AWS APIs, and you need a private endpoint.
Step 2 โ Create the VPC Interface Endpoint
In the AWS Console:
- Go to VPC โ Endpoints โ Create Endpoint
- Service category: AWS services
- Search
secretsmanager, selectcom.amazonaws.<region>.secretsmanager - Pick your VPC
- Select all private subnets where your workloads run
- Attach a Security Group allowing inbound HTTPS (port 443) from your workload's security group
- Enable Private DNS โ this is the critical part
Prefer the CLI? Here it is:
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc123def456 \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.secretsmanager \
--subnet-ids subnet-0aaa111 subnet-0bbb222 \
--security-group-ids sg-0xxxxxx \
--private-dns-enabled \
--region us-east-1
Don't skip --private-dns-enabled. Without it the SDK still tries to hit the public endpoint and you're back to square one.
Step 3 โ Enable DNS hostnames on the VPC
Private DNS only kicks in when the VPC has both DNS hostnames and DNS resolution enabled. Older VPCs sometimes have these off. Check first:
# Check current setting
aws ec2 describe-vpc-attribute \
--vpc-id vpc-0abc123def456 \
--attribute enableDnsHostnames
aws ec2 describe-vpc-attribute \
--vpc-id vpc-0abc123def456 \
--attribute enableDnsSupport
# Enable if disabled
aws ec2 modify-vpc-attribute \
--vpc-id vpc-0abc123def456 \
--enable-dns-hostnames
aws ec2 modify-vpc-attribute \
--vpc-id vpc-0abc123def456 \
--enable-dns-support
Step 4 โ Update the endpoint's Security Group
The interface endpoint is a real network interface โ it has its own security group. Port 443 must be open inbound from your Lambda, EC2, or ECS workloads:
aws ec2 authorize-security-group-ingress \
--group-id sg-0endpoint-sg \
--protocol tcp \
--port 443 \
--source-group sg-0your-workload-sg \
--region us-east-1
Missed security group rules are one of the top reasons the endpoint exists but connections still fail.
Step 5 โ Attach an Endpoint Policy (optional but recommended)
Out of the box, the endpoint grants full Secrets Manager access to any principal in your account. Lock it down to exactly what your app needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/MyAppRole"
},
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:MyApp/*"
}
]
}
Apply via Console (Endpoint โ Policy tab) or CLI:
aws ec2 modify-vpc-endpoint \
--vpc-endpoint-id vpce-0xxxxxxxxx \
--policy-document file://endpoint-policy.json
Verify the Fix
The endpoint takes 1โ2 minutes to reach Available state. Once it does, test from inside the private subnet:
# DNS should now resolve to a private IP (10.x.x.x or 172.x.x.x)
nslookup secretsmanager.us-east-1.amazonaws.com
# Fetch a secret โ should return immediately
aws secretsmanager get-secret-value \
--secret-id MyApp/prod/database \
--region us-east-1
# Or with boto3
python3 -c "
import boto3
client = boto3.client('secretsmanager', region_name='us-east-1')
resp = client.get_secret_value(SecretId='MyApp/prod/database')
print(resp['SecretString'][:50])
"
If nslookup returns an IP inside your VPC range โ say 10.0.1.45 instead of a public AWS IP โ and the secret value returns cleanly, you're done.
Tips
Other endpoints you'll likely need
Secrets Manager is rarely the only AWS service a private workload calls. Each one needs its own VPC endpoint โ or you're back to needing a NAT Gateway. Common additions:
com.amazonaws.<region>.kmsโ when secrets use a customer-managed keycom.amazonaws.<region>.stsโ for IAM role assumptioncom.amazonaws.<region>.ssmโ for Parameter Store
Subnet CIDR planning
Before attaching the endpoint across multiple AZs, double-check that your subnet CIDRs are sized correctly and don't overlap. The browser-based Subnet Calculator on ToolCraft is handy for quickly verifying ranges and counting available IPs without doing the binary math yourself.
Cost consideration
Interface endpoints aren't free: roughly $0.01/hour per AZ, plus data transfer fees. Spread across three AZs, that's ~$21/month per endpoint. If you're running a dozen services in private subnets, a single NAT Gateway at $32/month flat may actually cost less. Run the numbers against your traffic before adding an endpoint per service.
Terraform snippet
Managing this as infrastructure code:
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoint.id]
private_dns_enabled = true
tags = {
Name = "secretsmanager-endpoint"
}
}

