Amazon EKS — Private Cluster Access

5 minute read

In production, you should restrict who can talk to your EKS API server. EKS supports three API server endpoint access modes. Choosing the right one is one of the most important security decisions you’ll make.


API Server Endpoint Access Modes

Mode Public Private Who Can Reach the API
Public (default) Yes No Anyone on the internet (filtered by CIDR)
Public + Private Yes Yes Internet + nodes within VPC use private endpoint
Private No Yes Only from within the VPC or peered/connected networks

How Each Mode Works

Public Endpoint (Default)

Internet ──→ Public API Endpoint ──→ EKS Control Plane
                                         │
Nodes ──→ Public API Endpoint ────────────┘
          (via NAT Gateway)
  • API server is accessible from the internet
  • Worker nodes reach the API via NAT Gateway → internet → public endpoint
  • You can restrict by CIDR (e.g., only your office IP)
  • Not recommended for production — nodes shouldn’t route control plane traffic through the internet

Public + Private Endpoint

Internet ──→ Public API Endpoint ──→ EKS Control Plane
                                         │
Nodes ──→ Private API Endpoint ──────────┘
          (via VPC ENIs, no internet)
  • External access is still available (for your laptop, CI/CD)
  • Nodes communicate through the private endpoint (within VPC)
  • Node traffic never leaves the VPC
  • Good compromise for most production environments

Private Endpoint Only

Internet ──✗ (blocked)

VPN/Direct Connect ──→ Private API Endpoint ──→ EKS Control Plane
                                                      │
Nodes ──→ Private API Endpoint ───────────────────────┘
  • API server is only accessible from within the VPC
  • You need VPN, Direct Connect, or a bastion host to access the cluster
  • Most secure option
  • Recommended for high-security environments

Configuring Endpoint Access

Using AWS CLI

# Public + Private (recommended for most)
aws eks update-cluster-config \
  --name my-cluster \
  --resources-vpc-config \
    endpointPublicAccess=true,endpointPrivateAccess=true,publicAccessCidrs=["203.0.113.0/24"]

# Private only
aws eks update-cluster-config \
  --name my-cluster \
  --resources-vpc-config \
    endpointPublicAccess=false,endpointPrivateAccess=true

Using eksctl

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: my-cluster
  region: us-east-1
vpc:
  clusterEndpoints:
    publicAccess: true
    privateAccess: true
  publicAccessCIDRs:
    - "203.0.113.0/24"    # Your office IP
    - "198.51.100.0/24"   # VPN egress IP

Using Terraform

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "my-cluster"
  cluster_version = "1.29"

  cluster_endpoint_public_access  = true
  cluster_endpoint_private_access = true

  cluster_endpoint_public_access_cidrs = [
    "203.0.113.0/24",    # Office IP
    "198.51.100.0/24",   # VPN egress
  ]

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets
}

Private Cluster Access Patterns

When your API endpoint is private-only, you need a way to reach it. Here are the common patterns:

Pattern 1 — Bastion Host (Jump Box)

Your Laptop ──SSH──→ Bastion (public subnet) ──kubectl──→ EKS API (private)
# SSH into the bastion
ssh -i my-key.pem ec2-user@bastion-public-ip

# Run kubectl from the bastion
kubectl get nodes

Pros: Simple, well-understood Cons: Another server to manage, SSH key management, single point of access

Pattern 2 — AWS Systems Manager Session Manager

Your Laptop ──SSM──→ EC2 Instance (private subnet) ──kubectl──→ EKS API (private)

No SSH keys, no public IP, no bastion needed. SSM uses the AWS API to establish a session.

# Start a session (no SSH needed)
aws ssm start-session --target i-1234567890abcdef0

# Port forward kubectl to your laptop
aws ssm start-session \
  --target i-1234567890abcdef0 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{
    "host": ["ABCDEF1234.gr7.us-east-1.eks.amazonaws.com"],
    "portNumber": ["443"],
    "localPortNumber": ["6443"]
  }'

Pros: No SSH keys, audit trail in CloudTrail, no public IP needed Cons: Requires SSM agent on EC2, slight latency

Pattern 3 — VPN (AWS Client VPN or Site-to-Site)

Your Laptop ──VPN──→ VPC ──kubectl──→ EKS API (private)
# Create Client VPN endpoint
aws ec2 create-client-vpn-endpoint \
  --client-cidr-block "10.100.0.0/16" \
  --server-certificate-arn arn:aws:acm:us-east-1:123456789:certificate/abc \
  --authentication-options Type=certificate-authentication,MutualAuthentication={ClientRootCertificateChainArn=arn:aws:acm:us-east-1:123456789:certificate/def} \
  --connection-log-options Enabled=false \
  --vpc-id vpc-123456

Pros: Full VPC access from laptop, transparent to kubectl Cons: VPN setup complexity, Client VPN costs (~$0.10/hr + connection fees)

Pattern 4 — AWS Direct Connect

Corporate DC ──Direct Connect──→ VPC ──kubectl──→ EKS API (private)

Pros: Dedicated, low-latency, high-bandwidth private connection Cons: Expensive, long provisioning time (weeks/months)

Pattern 5 — VPC Peering / Transit Gateway

For CI/CD in another VPC:

CI/CD VPC ──Peering/TGW──→ EKS VPC ──→ EKS API (private)
# Create peering connection
aws ec2 create-vpc-peering-connection \
  --vpc-id vpc-cicd \
  --peer-vpc-id vpc-eks

# Update route tables to route EKS private endpoint traffic
aws ec2 create-route \
  --route-table-id rtb-cicd \
  --destination-cidr-block 10.0.0.0/16 \
  --vpc-peering-connection-id pcx-123456

VPC Endpoints for Private Clusters

When the API server is private-only, nodes still need to reach AWS services for: pulling ECR images, writing CloudWatch logs, downloading EKS add-ons.

Without VPC endpoints, nodes need a NAT Gateway for internet access. VPC endpoints eliminate that dependency (and save money):

Endpoint Service Why Needed
com.amazonaws.region.ec2 EC2 API VPC CNI calls EC2 APIs
com.amazonaws.region.ecr.api ECR API Image pull authentication
com.amazonaws.region.ecr.dkr ECR Docker Image layer downloads
com.amazonaws.region.s3 S3 (Gateway) ECR image layers stored in S3
com.amazonaws.region.sts STS IRSA / Pod Identity token exchange
com.amazonaws.region.logs CloudWatch Logs Container logging
com.amazonaws.region.elasticloadbalancing ELB AWS LB Controller
com.amazonaws.region.autoscaling Auto Scaling Cluster Autoscaler

Creating VPC Endpoints

# Interface endpoint (ECR API)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-123456 \
  --service-name com.amazonaws.us-east-1.ecr.api \
  --vpc-endpoint-type Interface \
  --subnet-ids subnet-private-a subnet-private-b \
  --security-group-ids sg-vpc-endpoint

# Gateway endpoint (S3 — free)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-123456 \
  --service-name com.amazonaws.us-east-1.s3 \
  --vpc-endpoint-type Gateway \
  --route-table-ids rtb-private

Cost: Interface endpoints cost ~$0.01/hr + $0.01/GB. Gateway endpoints (S3, DynamoDB) are free.


DNS Considerations for Private Clusters

When you enable the private endpoint, EKS creates a private hosted zone in Route 53 that resolves the API server hostname to private IPs within your VPC.

# From within the VPC, the API hostname resolves to private IPs
nslookup ABCDEF1234.gr7.us-east-1.eks.amazonaws.com
# Returns: 10.0.1.55, 10.0.2.66

# From outside the VPC (if public endpoint disabled), it returns nothing useful

If you’re connecting via VPN or VPC peering, ensure DNS resolution works across the connection:

# Enable DNS resolution for VPC peering
aws ec2 modify-vpc-peering-connection-options \
  --vpc-peering-connection-id pcx-123456 \
  --requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true

Restricting Public Access with CIDRs

If you use public + private mode, restrict the public endpoint to trusted IPs:

aws eks update-cluster-config \
  --name my-cluster \
  --resources-vpc-config publicAccessCidrs='["203.0.113.0/24","198.51.100.0/32"]'

Best practice: Only allow your office IP, VPN egress IPs, and CI/CD runner IPs. Never leave it open to 0.0.0.0/0.


Comparison of Access Patterns

Pattern Setup Complexity Cost Security Best For
Public endpoint + CIDR restriction Low Free Medium Small teams, learning
Public + private + CIDR Low Free Good Most production clusters
Private + bastion Medium ~$10/month High Simple private access
Private + SSM Medium Free (SSM included) High AWS-native teams
Private + VPN High ~$75+/month High Multiple developers
Private + Direct Connect Very High \(\) Very High Enterprise, hybrid

Best Practices

  1. Never use public-only endpoint in production — at minimum, enable private too
  2. Restrict public CIDRs — don’t leave the public endpoint open to the world
  3. Use VPC endpoints to avoid NAT Gateway dependency and reduce data transfer costs
  4. Prefer SSM Session Manager over bastion hosts — no SSH key management, built-in audit trail
  5. Enable CloudTrail for API server access — all kubectl commands are logged as API calls
  6. Plan DNS resolution when accessing private clusters from peered VPCs or VPNs
  7. Consider the team workflow — developers need a smooth way to run kubectl; don’t make it so painful they avoid using it

Updated: