Amazon EKS Private Clusters: Endpoint Access and kubectl
A private EKS cluster restricts access to the Kubernetes API server so it is not reachable from the public internet. This shrinks the attack surface on a critical control-plane component, but it introduces real operational complexity: kubectl access, CI/CD pipelines, and node registration all need re-thinking. Most teams should start with public + private with CIDR restrictions rather than jumping straight to private-only. This guide explains what private access means in EKS, how each mode works, and how to make the right choice for your environment.
What is a private EKS cluster?
In plain terms: every EKS cluster has a DNS hostname for its Kubernetes API server. By default, that hostname resolves to a public IP address, meaning anyone on the internet can attempt a connection (they still need valid AWS credentials, but the network port is open). Making the cluster private means that hostname resolves to a private IP address inside your VPC instead. Traffic can only reach the API server if it originates from inside the VPC, or from a network directly connected to it via VPN or Direct Connect.
This is specifically about control-plane access: who can reach the Kubernetes API to run commands or deploy workloads. It is not a magic switch that makes everything in the cluster secure. Your pods, load balancers, and container images are governed by separate controls.
A private endpoint is one layer of security. It works alongside IAM roles for service accounts, Kubernetes RBAC, network policies, and pod security standards. Not instead of them.
Analogy Think of the Kubernetes API server as the front desk of an office building. In public mode, the front desk is on the street — anyone can walk up and try to check in (they still need an access badge, but the desk is visible and reachable). In private mode, the front desk is moved to an inner room that can only be reached from inside the building. You still need your badge to access anything, but the desk itself is no longer accessible to someone walking past on the pavement.
How private endpoint access works in EKS
EKS manages the Kubernetes control plane (API server, etcd, scheduler) on AWS-managed infrastructure. The API server is exposed via a DNS hostname such as ABC123.gr7.eu-west-1.eks.amazonaws.com. You control how that hostname resolves and who can reach it.
Public endpoint: The hostname resolves to a public IP. Requests from anywhere on the internet can reach the API server. IAM authentication is required, but there is no network-level restriction on who can attempt a connection. This is the default.
Private endpoint: When you enable private access, EKS creates elastic network interfaces (ENIs) inside your VPC and manages a Route 53 private hosted zone so the cluster hostname resolves to a private IP from within the VPC. Traffic from inside the VPC goes directly to the API server over the private network with no internet hop.
Both enabled (public + private): The public DNS endpoint still exists, but you can restrict which CIDR ranges can reach it. Traffic from inside the VPC uses the private endpoint automatically. This is the most common configuration for production teams.
Private only: The public endpoint is disabled. The cluster DNS hostname only resolves from inside the VPC or connected networks. Every kubectl client and automation tool must have a private network path to the VPC.
A few technical details worth knowing:
- Your VPC must have
enableDnsSupportandenableDnsHostnamesboth set to true. Without these, the Route 53 private hosted zone does not resolve correctly and nodes cannot contact the API server. - Access to the private endpoint is governed by the cluster security group. Traffic must be allowed on port 443 from the subnets or instances that need API access.
- EKS manages the private hosted zone and DNS records automatically when private access is enabled. You do not create DNS records manually.
Endpoint access modes compared
| Mode | Public internet exposure | How kubectl works | Node-to-API path | Operational complexity | Best fit |
|---|---|---|---|---|---|
| Public only (default) | API server reachable from anywhere on the internet | Works from any machine with valid credentials | Nodes reach API over public internet | Low | Learning, short-lived clusters, development |
| Public + private | Public endpoint exists but restricted to approved CIDRs | Works from approved IPs; VPN required for all others | Nodes use private endpoint inside VPC | Medium | Most production workloads |
| Private only | No public exposure | Requires VPN, bastion, or in-VPC access | Nodes use private endpoint inside VPC | High | Regulated environments, highest security posture |
When to use each option
Use public only when:
- You are learning Kubernetes or EKS
- You are running a short-lived test or demo cluster
- Operational simplicity matters more than network hardening at this stage
Use public + private when:
- You want to reduce public attack surface without eliminating direct kubectl access
- Your team runs kubectl from a fixed office IP range or VPN exit node
- Your CI/CD pipeline has a static IP you can include in the allow-list
- You want a strong production default that most compliance reviews accept without building out VPN or bastion infrastructure
Use private only when:
- A compliance or regulatory framework explicitly requires no public API server exposure (PCI-DSS Level 1, FedRAMP High, some HIPAA interpretations)
- Your organisation already has a VPN or Direct Connect path to AWS that all engineers use day-to-day
- You have the operational maturity to manage private access for kubectl, CI/CD, and all tooling that interacts with the cluster
Private only is likely overkill when:
- Your team has no existing VPN or Direct Connect infrastructure
- You have no established pattern for running CI/CD runners inside the VPC
- The compliance requirement you are working toward does not explicitly mandate it
Public + private: the practical default for most teams
The public + private mode gives you most of the security benefit of a private cluster while keeping kubectl working from approved locations. The public endpoint is still there, but you restrict it to specific CIDR ranges (your office network, your CI/CD system’s IP, VPN exit nodes) and block everything else.
Traffic from worker nodes inside the VPC always uses the private endpoint, which means node-to-API communication never travels over the internet even while the public endpoint is technically enabled.
# Enable both endpoints and restrict public access to specific CIDRs
aws eks update-cluster-config \
--name my-cluster \
--region eu-west-1 \
--resources-vpc-config \
endpointPublicAccess=true,\
endpointPrivateAccess=true,\
publicAccessCidrs="203.0.113.0/24,198.51.100.0/24"What this achieves:
- Developers at the office (
203.0.113.0/24) can run kubectl from their laptops without a VPN. - The CI/CD pipeline at
198.51.100.0/24can deploy to the cluster directly. - All other public internet traffic to the API server is dropped at the network level before reaching any authentication layer.
- Worker nodes inside the VPC route API server communication through the private endpoint.
This is a meaningful security improvement over public-only. The API server is still reachable from those specific CIDRs, but the exposure is dramatically reduced. For more on how VPC CNI and pod-level networking fits into this picture, see the EKS networking model guide.
CIDR restrictions on the public endpoint are enforced at the AWS network level, not inside the API server. Traffic from non-approved CIDRs is dropped before it reaches Kubernetes authentication. This is a network-layer control, not an application-level filter.
Private-only clusters
Disabling the public endpoint entirely means the API server is only reachable from inside the VPC or from connected networks. This is the highest security posture for control-plane access and is required in some regulated environments.
When you create a private-only cluster with eksctl, the privateCluster option also handles the VPC endpoint creation that your nodes need to reach AWS services:
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: private-cluster
region: eu-west-1
privateCluster:
enabled: true
additionalEndpointServices:
- "autoscaling"
- "ec2"
- "ecr.api"
- "ecr.dkr"
- "s3"
vpc:
clusterEndpoints:
publicAccess: false
privateAccess: trueSetting privateCluster: enabled: true tells eksctl to create the Interface VPC Endpoints that nodes in private subnets need to communicate with EC2, ECR, STS, and other services without going through the internet. Endpoint management is handled for you, which is one of the biggest operational benefits of using eksctl for private clusters.
What changes operationally when you go private-only:
- kubectl from your laptop stops working unless you are connected via VPN or Direct Connect
- CI/CD pipelines running from public managed runners need to move inside the VPC or connect via VPN
- Any tooling that calls the Kubernetes or EKS API (Cluster Autoscaler, Flux, ArgoCD, external-dns) must run inside the VPC or have a private network path
- Debugging and incident response become slower because every access point requires an additional network hop
If you are creating a new EKS cluster and know you will need private-only access, it is much easier to configure this from the start. Retrofitting a public cluster to private-only later is possible but requires careful planning around access continuity.
Never disable the public endpoint until you have verified that an alternative access path works end-to-end. If you disable public access first and your private path is misconfigured, you will lose all kubectl access to the cluster immediately. There is no rollback without contacting AWS Support.
VPC requirements for fully private clusters
A fully private EKS cluster requires more VPC infrastructure than a public one. Worker nodes in private subnets need outbound connectivity to reach AWS services: ECR to pull container images, EC2 to register with the cluster, STS to assume IAM roles, S3 for bootstrap scripts, and CloudWatch for cluster logs and metrics.
Without either a NAT gateway or the required VPC endpoints, nodes will silently fail to join. The cluster itself creates successfully, but nodes never reach the Ready state. This is the most common source of frustration when building a private cluster for the first time. Understanding the EKS networking model in detail makes this easier to reason about before you start provisioning infrastructure.
This failure is silent. EKS reports the cluster as Active even when nodes cannot join because of missing endpoints. You will not see an error until you check node status and find them stuck at NotReady. Always verify your VPC endpoint configuration in a non-production environment before applying it to production.
Minimum network checklist for a fully private EKS cluster#
- Private subnets in at least two Availability Zones for worker nodes
- VPC DNS:
enableDnsSupport = trueandenableDnsHostnames = true - Outbound connectivity for nodes: NAT gateway or VPC Interface Endpoints (see next section)
- S3 Gateway Endpoint (free, required for ECR image layer downloads)
- VPC Interface Endpoint for
ecr.api - VPC Interface Endpoint for
ecr.dkr - VPC Interface Endpoint for
ec2 - VPC Interface Endpoint for
sts - VPC Interface Endpoint for
logs(if sending node logs to CloudWatch) - Endpoint security groups allow HTTPS (port 443) inbound from the node security group
- Cluster security group allows HTTPS inbound from the node security group
- Private network access path for kubectl: VPN, bastion, or in-VPC runner
NAT Gateway vs VPC Endpoints
Nodes in private subnets need outbound connectivity. The two main approaches have different trade-offs:
| Aspect | NAT Gateway | VPC Interface Endpoints |
|---|---|---|
| How it works | Nodes route outbound traffic through a NAT gateway in a public subnet. The NAT gateway translates private IPs to a public IP for internet access. | Creates private ENIs inside your VPC for each AWS service. Traffic routes over the AWS private network and never touches the internet. |
| Setup complexity | One NAT gateway covers all outbound traffic. Simple to configure. | One endpoint per service, per Availability Zone. Each needs a security group and DNS configuration. |
| Cost model | Hourly charge per NAT gateway plus a per-GB data processing fee for all traffic passing through it. | Hourly charge per Interface Endpoint per AZ. No data processing fee for traffic to AWS services. S3 Gateway Endpoint is free. |
| Security posture | Traffic to AWS services crosses the internet (encrypted, but still internet-routed). | Traffic stays entirely within the AWS private network. Stronger isolation, no internet exposure. |
| Third-party images | Nodes can pull from Docker Hub, GitHub Container Registry, and other public registries. | No direct access to public registries. Use an ECR pull-through cache to mirror images from public sources. |
When a NAT gateway makes sense: You are pulling images from public registries, or you want simple outbound internet access without managing individual service endpoints. A good starting point when you are new to private clusters and want to iterate quickly.
When VPC endpoints make sense: All container images are in private ECR, your compliance requirements prohibit internet-routed traffic from nodes, or you want the strongest possible network isolation. This is the pattern that eksctl’s privateCluster option sets up automatically.
A practical approach: start with a NAT gateway to get the cluster working, then migrate to VPC endpoints once your image strategy and required service list are stable.
How to run kubectl against a private EKS cluster
When the public endpoint is disabled, your kubectl client must reach the API server through a private network path. Here are the main options and when each makes sense.
AWS Site-to-Site VPN Establishes an encrypted IPsec tunnel between your on-premises or office network and your AWS VPC. Once connected, all machines on that network can reach the private EKS endpoint as if they were inside the VPC. Best for: Teams with an existing VPN connection to AWS, or organisations migrating on-premises workloads to EKS.
AWS Direct Connect A dedicated private network link between your data centre and AWS. Provides lower latency and more predictable throughput than a VPN. Best for: Large organisations with high bandwidth requirements or latency-sensitive workloads that already have Direct Connect infrastructure in place.
Bastion host An EC2 instance in a public subnet that you SSH into. From the bastion you run kubectl against the private API server endpoint. The bastion has a public IP; the API server does not. The bastion can be stopped when not in use to save cost. Best for: Teams that need occasional manual access and want a simple, low-cost setup without building VPN infrastructure.
AWS Systems Manager Session Manager Connect to an EC2 instance inside the VPC without opening any SSH ports or managing key pairs. You start a session via the AWS Console or CLI, which tunnels through the SSM service. From the connected instance you run kubectl normally. Best for: Teams that want secure interactive access without a traditional bastion host. Works cleanly alongside deploying containers with kubectl from an in-VPC instance.
CI/CD runners inside the VPC Run pipeline agents (GitHub Actions self-hosted runners, GitLab Runners, Jenkins agents) on EC2 instances or as pods in the cluster itself. Automated deployments work without VPN because the runners are already on the private network. Best for: Production deployment pipelines. In mature teams this is usually the primary access method, with VPN or Session Manager reserved for manual debugging.
You can combine approaches. Many teams use self-hosted CI/CD runners for automated deployments and Systems Manager Session Manager for occasional manual access. No VPN infrastructure required for either workflow.
Common mistakes
- Disabling the public endpoint before private access is ready. If you turn off public access before a VPN, bastion, or in-VPC runner is configured and tested, you lose all kubectl access immediately. Always verify that your private access path works end-to-end before disabling the public endpoint. This is the single most disruptive mistake teams make with private clusters.
- Missing VPC endpoints or outbound internet access for nodes. Nodes in private subnets fail silently when they cannot reach EC2, STS, or ECR. The cluster creates successfully but nodes never reach Ready. Check cluster monitoring and CloudWatch logs to diagnose node registration failures, and verify every required endpoint exists before creating the cluster.
- Assuming private networking replaces other security controls. A private endpoint stops unauthenticated network access from the internet. It does not stop a compromised pod inside the VPC from calling the API server, does not enforce least-privilege IAM, and does not replace Kubernetes RBAC or network policies. Always use private endpoint access alongside IAM roles for service accounts, RBAC, and network policies. See securing EKS clusters for a complete picture.
- Forgetting CI/CD pipeline access. Teams that switch to private-only without updating their deployment pipelines discover that their CI/CD system (previously connecting to the public endpoint) suddenly cannot reach the cluster. Plan and test pipeline access before tightening endpoint settings in production.
- VPC DNS not enabled. Private endpoint resolution relies on a Route 53 private hosted zone. If
enableDnsSupportorenableDnsHostnamesis false on the VPC, the cluster hostname will not resolve to the private IP from inside the VPC. Nodes and kubectl clients will fail to connect even when the network path exists. - Incorrect security groups on VPC endpoints. Interface Endpoints have their own security groups. If the endpoint security group does not allow HTTPS (port 443) inbound from your node security group, traffic is silently dropped even though the endpoint exists and DNS resolves correctly.
Troubleshooting
kubectl cannot reach the cluster#
Likely cause: The public endpoint is disabled and there is no private network path from where you are running kubectl.
What to check: Confirm endpoint settings with aws eks describe-cluster --name <cluster> --query "cluster.resourcesVpcConfig". Verify your VPN or bastion is active. If using a bastion, confirm its security group allows outbound HTTPS (port 443) toward the API server endpoint hostname.
Nodes are not joining the cluster#
Likely cause: Missing VPC endpoints or no NAT gateway. Nodes cannot reach the EC2 API to register or the STS API to assume their IAM role.
What to check: Inspect node bootstrap logs via SSM Session Manager at /var/log/cloud-init-output.log. Verify endpoints exist for ec2, sts, ecr.api, ecr.dkr, and s3. Check that endpoint security groups allow HTTPS from the node security group. Confirm the node IAM role has AmazonEKSWorkerNodePolicy, AmazonEC2ContainerRegistryReadOnly, and AmazonEKS_CNI_Policy attached.
Image pulls fail in private subnets#
Likely cause: The ECR Interface Endpoints (ecr.api, ecr.dkr) are missing, or the S3 Gateway Endpoint is absent. ECR stores image layers in S3, so both are required for image pulls to succeed.
What to check: Run aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=<vpc-id> and verify both ECR endpoints and the S3 Gateway endpoint exist. Confirm the S3 Gateway endpoint includes route table associations for the private subnets. If pulling from public registries (Docker Hub, GHCR), you need either a NAT gateway or an ECR pull-through cache.
Cluster endpoint resolves to a public IP from inside the VPC#
Likely cause: VPC DNS settings are not enabled, or a conflicting DNS configuration is overriding the private hosted zone.
What to check: From a node or in-VPC instance, run nslookup <cluster-endpoint> or dig <cluster-endpoint>. The response should show a private IP (typically in the 10.x.x.x range). If it shows a public IP, check that enableDnsSupport and enableDnsHostnames are both true on the VPC. Also verify no custom DNS servers are overriding Route 53 resolution.
CI/CD pipeline cannot deploy#
Likely cause: The pipeline runner is outside the VPC and the public endpoint is disabled, or the runner’s IP is not in the public access CIDR allow-list.
What to check: Confirm whether the public endpoint is enabled: aws eks describe-cluster --name <cluster> --query "cluster.resourcesVpcConfig.endpointPublicAccess". If enabled, verify the runner’s IP is in publicAccessCidrs. If private-only, confirm the runner is inside the VPC or connected via VPN, and that its security group allows outbound HTTPS to the API server endpoint.
Private EKS cluster vs public + private EKS cluster
The practical question most teams ask is: do you need to fully disable the public endpoint, or is restricting it with CIDRs enough?
Public + private with CIDR restrictions:
- The API server has a public DNS name but only approved CIDRs can reach it
- kubectl works directly from your office or approved CI/CD system without requiring a VPN
- In-VPC traffic (nodes) uses the private endpoint automatically
- Most security and compliance reviews accept this configuration
- Easier to operate, debug, and on-board new engineers to
Private only:
- No public DNS exposure whatsoever
- kubectl and all cluster tooling must have an in-VPC or VPN-connected path
- Strongest network isolation for the control plane
- Required for some regulated workloads under specific compliance frameworks
- Significantly higher operational overhead for everyday access and troubleshooting
For most teams, public + private with tight CIDR restrictions delivers the majority of the security benefit of private-only at a fraction of the operational cost. Choose private-only when you have a clear, documented requirement for it. Not just because it sounds more secure.
Both options still require IAM, RBAC, network policies, and workload security controls. Private endpoint access is a network boundary, not a substitute for identity and authorisation controls inside the cluster.
Summary
- EKS has three endpoint access modes: public only (default), public + private, and private only. Most production teams use public + private with CIDR restrictions.
- Private endpoint access means EKS creates ENIs in your VPC and manages a Route 53 private hosted zone so the cluster hostname resolves to a private IP from inside the VPC.
- Private-only clusters require VPN, Direct Connect, a bastion host, Systems Manager Session Manager, or in-VPC CI/CD runners for all kubectl access.
- Nodes in private subnets need outbound connectivity to EC2, STS, ECR, and S3. You provide this via a NAT gateway or VPC Interface Endpoints.
- VPC DNS settings (enableDnsSupport and enableDnsHostnames) must both be true or private endpoint resolution fails silently.
- Private endpoint access is one security layer. It works alongside IAM, RBAC, network policies, and pod security. Not instead of them.
Frequently asked questions
Can I switch an existing EKS cluster from public to private?
Yes. You can modify the API server endpoint access settings on a running cluster via the AWS Console or CLI without recreating it. The change takes a few minutes and does not affect running workloads. Always have a private access path (VPN, bastion, or in-VPC runner) ready and tested before disabling the public endpoint. If you disable it without a working alternative, you will lose all kubectl access immediately.
Does a private EKS cluster still need IAM and RBAC?
Absolutely. A private endpoint prevents unauthenticated network access from the internet, but it does not stop a compromised workload inside the VPC from reaching the API server. IAM authentication, Kubernetes RBAC, network policies, and pod security controls are all still necessary. Private networking is one layer of defence, not a complete security solution.
Do private EKS clusters require NAT gateways?
Not necessarily. Nodes in private subnets need outbound connectivity to reach AWS services like ECR, S3, EC2, and STS. You can provide this via a NAT gateway (simpler, costs money per hour and per GB transferred) or via VPC Interface Endpoints for each required service (more setup, keeps traffic entirely on the AWS private network). eksctl's privateCluster option creates the required VPC endpoints automatically.
How does kubectl work when the public endpoint is disabled?
Your kubectl client must have a private network path to the API server, which only resolves from inside the VPC or connected networks. Practical options include AWS Client VPN, AWS Site-to-Site VPN, Direct Connect, a bastion host, AWS Systems Manager Session Manager via an in-VPC instance, or a CI/CD runner running inside the VPC.
Why are nodes failing to join my private cluster?
The most common cause is missing VPC endpoints or no outbound internet path. Nodes in private subnets need to reach EC2 (to register), STS (to assume their IAM role), ECR (to pull images), and S3 (for bootstrap data). Verify that all required endpoints exist, their security groups allow HTTPS from the node security group, and that VPC DNS is enabled (enableDnsSupport and enableDnsHostnames must both be true).