Set Up AWS Client VPN with Mutual TLS
Build a secure Client VPN tunnel into a private VPC using mutual TLS auth.
Introduction
30 Second Summary
Think about how companies let employees work from home while still accessing internal servers that aren't on the public internet. That secure tunnel between your laptop and the company's private network is a VPN.
In this project, you will build a production-grade Client VPN setup on AWS that lets your Windows machine securely tunnel into a private network. You'll generate your own certificates, stand up the VPN infrastructure, connect through the encrypted tunnel, and SSH into a Linux instance that has zero internet exposure.
What You'll Build
By the end, you'll open the AWS VPN Client on your Windows machine, click Connect, and SSH straight into a private Linux instance that has no public IP and no internet gateway.
By the end of this project, you'll have:
- A working mutual TLS VPN tunnel you can connect from anywhere to reach a private EC2 instance that has no public IP and no internet gateway.
- Connection logs streaming to CloudWatch so you can see exactly when clients connect, disconnect, and why failures happen.
- A split-tunnel configuration where only VPC-bound traffic goes through the tunnel while your regular internet browsing stays on your local connection.
- Secret Mission: Intentionally break the VPN by removing auth rules, misconfiguring security groups, and corrupting routes, then diagnose each failure using CloudWatch logs and CLI commands.
Are there any prerequisites?
You'll need an AWS account with admin permissions and the AWS CLI already installed and configured on your Windows machine. This project walks you through installing the VPN-specific tools (Easy-RSA and the AWS VPN Client) from scratch.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
You're setting up a production-grade Client VPN on AWS that uses mutual TLS authentication to create an encrypted tunnel from your Windows machine into a private VPC subnet. This is the same pattern enterprises use to give remote engineers secure access to internal resources without exposing them to the internet.
Generate Certificates and Import to ACM
The VPN tunnel you are building relies on mutual TLS authentication. Both the server and the client must prove their identity with certificates before the tunnel opens.
Without a working PKI (Public Key Infrastructure), nothing else in this project functions. In this step, you will generate a full certificate chain and import it into AWS Certificate Manager so AWS can use those certificates when you create the VPN endpoint later.
In this step, get ready to:
- Download Easy-RSA and generate a CA, server certificate, and client certificate.
- Copy the certificate files to a clean working folder.
- Import the server and client certificates into AWS Certificate Manager.
Download Easy-RSA 3.2.6 and generate the certificate chain
Easy-RSA is a command-line tool that creates and manages certificates. AWS recommends it for Client VPN setups because it ships as a standalone zip with bundled OpenSSL for Windows. No extra dependencies needed.
- Download EasyRSA-3.2.6-win64.zip from the GitHub releases page:
https://github.com/OpenVPN/easy-rsa/releases/download/v3.2.6/EasyRSA-3.2.6-win64.zip
- Open the downloaded zip file and extract the folder to C:\EasyRSA-3.2.6.
- Press the Windows key, type cmd, and press Enter to open Command Prompt.
- Navigate to the Easy-RSA folder and launch the EasyRSA shell by running:
cd C:\EasyRSA-3.2.6
.\EasyRSA-Start.bat
What is EasyRSA-Start.bat?
This batch file opens a bundled POSIX shell (mksh/Win32) inside your Command Prompt. You will see the prompt change to #. From here, you run ./easyrsa commands using forward slashes, Unix-style.
- Initialize a fresh PKI directory by running:
./easyrsa init-pki
You should see a message that says init-pki complete; you may now create a CA or requests.
- Build the Certificate Authority by running:
./easyrsa build-ca nopass
What will this command ask me?
You will be prompted for a Common Name. The default is Easy-RSA CA. Press Enter to accept the default. This name is for display purposes only and does not affect how the VPN works.
You should see CA creation complete in the output.
- Generate the server certificate by running:
./easyrsa --san=DNS:server build-server-full server nopass
What does --san=DNS:server do?
The --san=DNS:server flag adds a Subject Alternative Name to the certificate. Modern TLS implementations require a SAN entry. Without it, the VPN endpoint would reject the certificate.
- Generate the client certificate by running:
./easyrsa build-client-full client1.domain.tld nopass
You should see output confirming the keypair and certificate were created for client1.domain.tld.
- Exit the EasyRSA shell by running:
exit
Seeing a permission error?
Easy-RSA needs write access to the folder it lives in. If you extracted to a protected location like C:\Program Files, move the EasyRSA-3.2.6 folder to C:\ and try again.
help me fix Easy-RSA permission errors on Windows
Copy certificates to a working folder
The certificates are scattered across subdirectories inside the pki folder. Copying them to one place makes the ACM import step cleaner.
- Back in your regular Command Prompt (you exited the EasyRSA shell), create the working folder and copy the certificates by running:
mkdir C:\vpn-certs
copy pki\ca.crt C:\vpn-certs
copy pki\issued\server.crt C:\vpn-certs
copy pki\private\server.key C:\vpn-certs
copy pki\issued\client1.domain.tld.crt C:\vpn-certs
copy pki\private\client1.domain.tld.key C:\vpn-certs
What are these files?
- ca.crt is the root CA certificate that signs everything.
- server.crt and server.key are the server's identity. AWS uses these to prove the VPN endpoint is legitimate.
- client1.domain.tld.crt and client1.domain.tld.key are your client's identity. Your machine uses these to prove it is allowed to connect.
- Switch into the working folder and verify all five files are present by running:
cd C:\vpn-certs
dir
You should see ca.crt, server.crt, server.key, client1.domain.tld.crt, and client1.domain.tld.key listed.
Missing files in the directory?
Make sure you ran the copy commands from the C:\EasyRSA-3.2.6 folder (where the pki directory lives). If you already moved to C:\vpn-certs, navigate back first with cd C:\EasyRSA-3.2.6 and re-run the copy commands.
help me find my Easy-RSA certificate files
Import certificates into AWS Certificate Manager
ACM (AWS Certificate Manager) stores the certificates so that other AWS services can reference them by ARN. The Client VPN endpoint you create in the next step needs these ARNs.
- Make sure you are in the C:\vpn-certs folder, then import the server certificate by running:
aws acm import-certificate --certificate fileb://server.crt --private-key fileb://server.key --certificate-chain fileb://ca.crt
The command returns a JSON object with a CertificateArn field. Copy this value and save it somewhere. This is your server cert ARN.
- Import the client certificate by running:
aws acm import-certificate --certificate fileb://client1.domain.tld.crt --private-key fileb://client1.domain.tld.key --certificate-chain fileb://ca.crt
Copy the returned CertificateArn value and save it as your client cert ARN.
Why two separate certificates?
Mutual TLS means both sides verify each other. The server certificate tells your machine that the AWS endpoint is real. The client certificate tells AWS that your machine is allowed to connect.
Since both certificates are signed by the same CA, you can actually use the server cert ARN as the ClientRootCertificateChainArn when creating the endpoint later. AWS uses the CA chain embedded in that cert to validate incoming client connections.
Before you run the next command, what status do you expect to see for your certificates?
- Verify both certificates are in ACM by running:
aws acm list-certificates
You should see both certificates listed with Status showing ISSUED. One has a domain name of server and the other client1.domain.tld.
Certificate not showing as ISSUED?
If you see no certificates or the status is different, check that your AWS CLI is configured for the correct region. Certificates are regional. Run aws configure get region to confirm your default region.
help me troubleshoot my ACM certificate import
Your certificate chain is ready and AWS knows about it. Next up, you will create the VPC infrastructure and the Client VPN endpoint that uses these certificates to authenticate connections.
Create VPC Infrastructure and Client VPN Endpoint
Your certificates are imported and ready. Now they need infrastructure to protect. A VPN tunnel is only useful if it connects you to something that's otherwise unreachable from the public internet.
In this step, you'll create a VPC with a private subnet that has no internet gateway, a CloudWatch log group for connection diagnostics, and the Client VPN endpoint that will terminate your encrypted tunnel.
In this step, get ready to:
- Create a VPC and private subnet with no internet access.
- Set up CloudWatch connection logging.
- Create a Client VPN endpoint with mutual TLS and split-tunnel routing.
Create the VPC and private subnet
Your VPN needs a destination. You'll create a VPC with a private subnet that has zero internet exposure. The only way to reach resources in this subnet will be through the VPN tunnel you're building.
AWS CLI
- Create the VPC by running this command in your Command Prompt:
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=vpn-lab-vpc}]"
The output returns a JSON object. Find the VpcId field (format: vpc-xxxxxxxxxxxxxxxxx) and note it down. You'll need it in the next commands.
AWS Console
- Open the AWS Management Console and navigate to the VPC service. You can search for VPC in the top search bar.
- In the left sidebar, click Your VPCs.
- Click Create VPC in the top right.
- Select VPC only as the resource to create.
- In the Name tag field, enter vpn-lab-vpc.
- In the IPv4 CIDR block field, enter 10.0.0.0/16.
- Leave all other settings at their defaults.
- Click Create VPC.
The details page opens for your new VPC. Find the VPC ID field at the top (format: vpc-xxxxxxxxxxxxxxxxx) and note it down. You'll need it in the next steps.
What does creating a VPC do?
This creates a virtual network (VPC) with the IP range 10.0.0.0/16. That gives you 65,536 possible IP addresses to work with inside your isolated cloud network.
The name vpn-lab-vpc helps you identify this VPC later in the console or CLI output.
Now you need a subnet inside this VPC. This subnet will be private because you won't attach an internet gateway to it.
AWS CLI
- Create the private subnet by running this command:
aws ec2 create-subnet --vpc-id [[VPC_ID="your VPC ID from the previous command"]] --cidr-block 10.0.1.0/24 --availability-zone [[AZ="your availability zone e.g. us-east-1a"]] --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=vpn-lab-private-subnet}]"
Find the SubnetId field in the output (format: subnet-xxxxxxxxxxxxxxxxx) and note it down.
AWS Console
- In the VPC console left sidebar, click Subnets.
- Click Create subnet.
- In the VPC ID dropdown, select vpn-lab-vpc.
- In the Subnet name field, enter vpn-lab-private-subnet.
- In the Availability Zone dropdown, select any zone (e.g. the first one listed).
- In the IPv4 subnet CIDR block field, enter 10.0.1.0/24.
- Click Create subnet.
The subnet details page opens. Find the Subnet ID (format: subnet-xxxxxxxxxxxxxxxxx) and note it down.
What does this subnet do?
This carves out a smaller block of 256 IPs (10.0.1.0/24) inside your VPC. The availability zone places it in a specific data center zone within your region.
Because you are not creating or attaching an internet gateway, this subnet is completely private. Nothing in here can reach the internet, and the internet cannot reach it.
🙋♀️ Getting an authorization error?
Make sure your AWS CLI is configured with admin-level permissions. Run aws sts get-caller-identity to confirm you're using the correct account and role.
If you see an InvalidParameterValue error on the availability zone, check that your AZ matches your configured region. For example, if your region is us-east-1, valid zones include us-east-1a, us-east-1b, etc.
help me fix an AWS CLI authorization error when creating a VPC
Create CloudWatch log group for connection logging
When something goes wrong with a VPN connection, logs are your only diagnostic tool. You'll create a CloudWatch log group that captures every connect, disconnect, and failure event.
AWS CLI
- Create the log group by running:
aws logs create-log-group --log-group-name /aws/clientvpn
This command produces no output on success. Confirm the log group exists by running:
aws logs describe-log-groups --log-group-name-prefix /aws/clientvpn
You should see a JSON response containing logGroupName set to /aws/clientvpn. This confirms the log group is ready.
AWS Console
- Open the AWS Management Console and navigate to the CloudWatch service. Search for CloudWatch in the top search bar.
- In the left sidebar, expand Logs and click Log groups.
- Click Create log group.
- In the Log group name field, enter /aws/clientvpn.
- Leave all other settings at their defaults.
- Click Create.
You should see /aws/clientvpn appear in your log groups list. This confirms it is ready.
Seeing a ResourceAlreadyExistsException?
This means a log group with that name already exists in your account. That's fine. You can continue to the next substep.
help me understand why my CloudWatch log group already exists
Create the Client VPN endpoint
The Client VPN endpoint is the AWS-managed resource that terminates your encrypted tunnel. It handles the TLS handshake, validates certificates, and routes traffic into your VPC.
AWS CLI
- Create the endpoint by running this command:
aws ec2 create-client-vpn-endpoint --client-cidr-block 10.2.0.0/22 --server-certificate-arn [[SERVER_CERT_ARN="your server certificate ARN from Step 1"]] --authentication-options Type=certificate-authentication,MutualAuthentication={ClientRootCertificateChainArn=[[SERVER_CERT_ARN="your server certificate ARN from Step 1"]]} --connection-log-options Enabled=true,CloudwatchLogGroup=/aws/clientvpn --split-tunnel --vpc-id [[VPC_ID="your VPC ID"]] --tag-specifications "ResourceType=client-vpn-endpoint,Tags=[{Key=Name,Value=vpn-lab-endpoint}]"
Find the ClientVpnEndpointId in the output (format: cvpn-endpoint-xxxxxxxxxxxxxxxxx). Note this down. You'll need it in every remaining step.
AWS Console
- In the VPC console left sidebar, click Client VPN Endpoints.
- Click Create Client VPN endpoint.
- In the Name tag field, enter vpn-lab-endpoint.
- In the Client IPv4 CIDR field, enter 10.2.0.0/22.
- In the Server certificate ARN dropdown, select the certificate with domain name server.
- Under Authentication options, check Use mutual authentication.
- In the Client certificate ARN dropdown, select the same server certificate (since both are signed by the same CA).
- Under Connection logging, select Yes.
- In the CloudWatch Logs log group name dropdown, select /aws/clientvpn.
- Check the Enable split-tunnel checkbox.
- In the VPC ID dropdown, select vpn-lab-vpc.
- Click Create Client VPN endpoint.
The details page shows your new endpoint. Find the Client VPN endpoint ID (format: cvpn-endpoint-xxxxxxxxxxxxxxxxx). Note this down. You'll need it in every remaining step.
What does this command do?
This is the core of your VPN setup. Here's what each flag configures:
- --client-cidr-block 10.2.0.0/22 assigns IP addresses to VPN clients from this range. It must not overlap with your VPC CIDR.
- --server-certificate-arn tells the endpoint which certificate to present to clients during the TLS handshake.
- --authentication-options enables mutual TLS. Since both your server and client certs were signed by the same CA, you use the server cert ARN for both parameters.
- --connection-log-options streams connection events to your CloudWatch log group.
- --split-tunnel means only VPC-bound traffic goes through the tunnel. Your regular internet browsing stays on your local connection.
💡 Why use the same ARN for both server and client?
When the server and client certificates are issued by the same Certificate Authority (CA), AWS lets you use the server certificate ARN for the ClientRootCertificateChainArn parameter. AWS uses this ARN to find the CA that signed both certificates, and then validates any connecting client cert against that CA.
🙋♀️ Getting an InvalidParameterValue error?
Double-check that your server certificate ARN is correct. Run aws acm list-certificates to see your imported certificates and their ARNs.
If you see an error about the client CIDR, verify it doesn't overlap with your VPC CIDR (10.0.0.0/16). The client CIDR 10.2.0.0/22 must also be at least /22 and no larger than /12.
help me fix a Client VPN endpoint creation error
Before you run the verification command below, what state do you think the endpoint will be in? It was just created but hasn't been associated with a subnet yet.
AWS CLI
- Verify the endpoint was created successfully by running:
aws ec2 describe-client-vpn-endpoints
You should see your endpoint listed with Status.Code showing pending-associate and SplitTunnel set to true.
AWS Console
- In the VPC console, click Client VPN Endpoints in the left sidebar.
- Select your vpn-lab-endpoint endpoint.
- Check the State column. It should show Pending-associate.
The endpoint exists but cannot accept connections yet because it has no subnet association.
What does pending-associate mean?
A Client VPN endpoint needs at least one subnet association before it can accept connections. The association gives the endpoint network interfaces (ENIs) inside your VPC. You'll create this association in the next step.
Your VPC, subnet, log group, and Client VPN endpoint are all created. Next up, you'll wire everything together by associating the endpoint with your subnet, adding authorization rules, and creating routes so the tunnel actually goes somewhere.
Associate, Authorize, and Route
Your Client VPN endpoint exists, but it is currently stuck in pending-associate state. It cannot accept any connections yet.
Three pieces must be in place before a client can reach your VPC through the tunnel: a target subnet association (gives the endpoint network interfaces inside your VPC), an authorization rule (controls which CIDR ranges clients can access), and an explicit route (tells split-tunnel mode where to send traffic). Without all three, the tunnel goes nowhere.
In this step, get ready to:
- Associate the endpoint with your private subnet.
- Add an authorization rule allowing access to the VPC CIDR.
- Add a split-tunnel route for VPC-bound traffic.
Associate the endpoint with your private subnet
A target network association links your Client VPN endpoint to a subnet in your VPC. This gives the endpoint elastic network interfaces so it can actually route traffic into the VPC.
AWS CLI
- Associate your endpoint with the private subnet by running this command:
aws ec2 associate-client-vpn-target-network --client-vpn-endpoint-id [[ENDPOINT_ID="your ClientVpnEndpointId (cvpn-endpoint-...)"]] --subnet-id [[SUBNET_ID="your SubnetId (subnet-...)"]]
AWS Console
- In the VPC console, click Client VPN Endpoints in the left sidebar.
- Select your vpn-lab-endpoint.
- Click the Target network associations tab at the bottom.
- Click Associate target network.
- In the VPC dropdown, select vpn-lab-vpc.
- In the Choose a subnet to associate dropdown, select vpn-lab-private-subnet.
- Click Associate.
What does this command do?
It tells AWS to create network interfaces for the VPN endpoint inside your private subnet. The immediate output shows Status.Code: "associating" and an AssociationId you can use to track progress.
This association takes 5-10 minutes to complete. You need to wait for the status to reach associated before proceeding.
AWS CLI
- Check the association status by running:
aws ec2 describe-client-vpn-target-networks --client-vpn-endpoint-id [[ENDPOINT_ID="your ClientVpnEndpointId (cvpn-endpoint-...)"]]
You will likely see "Code": "associating" in the Status field. Wait a few minutes and run the command again until you see "Code": "associated".
AWS Console
- In the VPC console, stay on your endpoint's Target network associations tab.
- Check the State column. It will show Associating initially.
- Click the refresh icon every 2-3 minutes until the state changes to Associated.
Speed up the wait
Re-run the describe command every 2-3 minutes. The transition from associating to associated typically takes 5-10 minutes. Do not proceed to the next substep until you see associated.
🙋♀️ Seeing association-failed?
Confirm the subnet belongs to the same VPC you specified when creating the endpoint. The SubnetId and VpcId must match what you used in Step 2.
help me fix my Client VPN target network association failure
Add an authorization rule for the VPC CIDR
Even after association, clients cannot access anything until you add an authorization rule. This rule defines which network ranges connected clients are allowed to reach.
AWS CLI
- Authorize all connected clients to reach the VPC CIDR by running:
aws ec2 authorize-client-vpn-ingress --client-vpn-endpoint-id [[ENDPOINT_ID="your ClientVpnEndpointId (cvpn-endpoint-...)"]] --target-network-cidr 10.0.0.0/16 --authorize-all-groups
AWS Console
- In the VPC console, select your vpn-lab-endpoint.
- Click the Authorization rules tab at the bottom.
- Click Add authorization rule.
- In the Destination network to enable access field, enter 10.0.0.0/16.
- Under Grant access to, select Allow access to all users.
- Click Add authorization rule.
What does this command do?
The --target-network-cidr 10.0.0.0/16 tells the endpoint that connected clients are authorized to reach any IP in your VPC's range.
The --authorize-all-groups flag means every client with a valid certificate can use this rule. In production, you would scope this to specific Active Directory or SAML groups.
AWS CLI
- Confirm the rule was added by running:
aws ec2 describe-client-vpn-authorization-rules --client-vpn-endpoint-id [[ENDPOINT_ID="your ClientVpnEndpointId (cvpn-endpoint-...)"]]
You should see an entry with DestinationCidr of 10.0.0.0/16 and Status.Code showing active.
AWS Console
- Stay on the Authorization rules tab for your endpoint.
- You should see an entry with Destination showing 10.0.0.0/16 and Status showing Active.
Auth rule not showing active?
Authorization rules can take a minute to become active. Re-run the describe command after 30 seconds. If it stays in authorizing state for more than 5 minutes, check your endpoint ID is correct.
help me debug my Client VPN authorization rule
Add a route for the VPC CIDR
Because you enabled split-tunnel mode, the endpoint only sends traffic through the tunnel for destinations that have an explicit route. Without this route, your VPN client would have no path to reach VPC resources.
AWS CLI
- Create a route for VPC traffic by running:
aws ec2 create-client-vpn-route --client-vpn-endpoint-id [[ENDPOINT_ID="your ClientVpnEndpointId (cvpn-endpoint-...)"]] --destination-cidr-block 10.0.0.0/16 --target-vpc-subnet-id [[SUBNET_ID="your SubnetId (subnet-...)"]]
AWS Console
- In the VPC console, select your vpn-lab-endpoint.
- Click the Route table tab at the bottom.
- Click Create route.
- In the Route destination field, enter 10.0.0.0/16.
- In the Target VPC Subnet ID dropdown, select vpn-lab-private-subnet.
- Click Create route.
Why is this needed with split-tunnel?
With split-tunnel disabled, all client traffic routes through the VPN. With split-tunnel enabled, only traffic matching an entry in the VPN route table goes through the tunnel. Everything else stays on the client's local internet connection.
By adding 10.0.0.0/16 as a destination, you are telling the client's VPN adapter to send only VPC-bound packets through the tunnel. Your regular browsing stays untouched.
Before you verify, do you think the route will appear immediately or take a few minutes like the association did?
AWS CLI
- Verify the route was added by running:
aws ec2 describe-client-vpn-routes --client-vpn-endpoint-id [[ENDPOINT_ID="your ClientVpnEndpointId (cvpn-endpoint-...)"]]
You should see a route with DestinationCidr of 10.0.0.0/16 and Status.Code showing active. Routes typically activate within a minute.
AWS Console
- Stay on the Route table tab for your endpoint.
- You should see an entry with Destination showing 10.0.0.0/16 and Status showing Active. Routes typically activate within a minute.
Route not appearing?
Make sure the subnet ID you used matches the same subnet from the association step. The route's target subnet must be an associated target network.
help me fix my Client VPN route creation
Your endpoint is fully wired up. It has network interfaces in your VPC, an authorization rule telling it who can go where, and a route telling split-tunnel clients which traffic enters the tunnel. Next up, you will connect from your Windows machine and prove the tunnel works.
Connect Through the Tunnel
Your Client VPN endpoint is fully wired on the AWS side. It has a subnet association, an authorization rule, and a route. But none of that matters until you prove a real client can actually connect through the tunnel.
In this step, you will export the endpoint's configuration file, embed your client certificates into it, and connect from your Windows machine using the AWS VPN Client. A successful connection gives you a VPN IP address from the 10.2.0.0/22 client CIDR range.
In this step, get ready to:
- Export and prepare the client configuration file with embedded certificates.
- Install the AWS VPN Client and create a connection profile.
- Connect through the tunnel and verify you receive a VPN IP address.
Download the client configuration file
The AWS VPN Client needs a .ovpn configuration file that tells it where the endpoint lives and how to authenticate. AWS can export this file directly from the endpoint.
- Export the client configuration file by running this command in Command Prompt:
aws ec2 export-client-vpn-client-configuration --client-vpn-endpoint-id [[ENDPOINT_ID="your Client VPN endpoint ID (cvpn-endpoint-...)"]] --output text > C:\vpn-certs\client-config.ovpn
What does this command do?
The export-client-vpn-client-configuration command fetches the OpenVPN-compatible configuration from your endpoint. The --output text flag writes it as raw text, and the > C:\vpn-certs\client-config.ovpn redirect saves it into your certificates folder.
- Confirm the file was created by running:
dir C:\vpn-certs\client-config.ovpn
You should see client-config.ovpn listed with a file size of a few kilobytes.
File not found or 0 bytes?
Make sure you replaced the endpoint ID placeholder with your actual cvpn-endpoint-... value from the previous step. Also confirm the endpoint association status is associated before exporting.
help me troubleshoot why my Client VPN config export produced an empty file
Embed the client certificate and key into the config file
The exported .ovpn file contains the server connection details, but it does not include your client certificate or private key. You need to embed both so the AWS VPN Client can present them during the mutual TLS handshake.
- Open C:\vpn-certs\client-config.ovpn in a text editor (Notepad works fine).
- Scroll to the very end of the file.
- Add the following two blocks at the bottom of the file:
<cert>
(paste full contents of client1.domain.tld.crt here)
</cert>
<key>
(paste full contents of client1.domain.tld.key here)
</key>
Where do I get the certificate contents?
Open C:\vpn-certs\client1.domain.tld.crt in another Notepad window and copy the entire contents (including the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines). Paste them between the <cert> tags.
Do the same for C:\vpn-certs\client1.domain.tld.key . Copy everything (including the -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- lines) and paste between the <key> tags.
- Save and close client-config.ovpn.
TLS handshake failed later?
If the VPN fails to connect with a TLS error later, it usually means the cert or key was not pasted completely. Open the file again and verify you have the full -----BEGIN... to -----END... block for both sections with no extra whitespace before <cert> or <key>.
help me fix a TLS key negotiation error in my Client VPN config
Install AWS VPN Client and connect
With your configuration file ready, you need the AWS VPN Client application to establish the tunnel. This is a free desktop app from AWS that handles the OpenVPN connection under the hood.
- Download the AWS VPN Client from the official download page:
- Select the Windows x64 download.
- Run the installer and follow the prompts to complete installation.
What gets installed?
The installer sets up the AWS VPN Client application along with the TAP-Windows driver. The TAP driver creates a virtual network adapter that the VPN tunnel uses. This is normal and required.
- Open the AWS VPN Client application. Press the Windows key, type AWS VPN Client, and press Enter.
- Click File in the top menu bar.
- Click Manage Profiles.
- Click Add Profile.
- In the Display Name field, enter VPN Lab.
- For VPN Configuration File, browse to C:\vpn-certs\client-config.ovpn and select it.
- Click Add Profile.
Your profile is now saved. Time to connect.
- Back in the main AWS VPN Client window, select the VPN Lab profile.
- Click Connect.
The status indicator will change to Connected once the tunnel is established. This may take 10-20 seconds while the TLS handshake completes.
Before you run the next command, what IP range do you expect the new VPN adapter to show? Think about the client CIDR you configured on the endpoint.
- Open a new Command Prompt window and verify your VPN IP by running:
ipconfig
Look for a new network adapter (TAP-Windows Adapter) with an IP address in the 10.2.x.x range. This confirms your machine received an IP from the 10.2.0.0/22 client CIDR and the tunnel is live.
Not seeing a Connected status?
If the client is stuck in a reconnecting state, verify your computer has internet connectivity. The client needs to resolve the endpoint DNS hostname before it can establish the tunnel.
If you see a TLS error, re-open client-config.ovpn and confirm the <cert> and <key> blocks contain the complete certificate and key text. Missing lines or extra whitespace will cause the handshake to fail.
If the VPN process quits unexpectedly, try re-running the AWS VPN Client installer to reinstall the TAP-Windows driver.
help me troubleshoot my AWS VPN Client connection failure
Your tunnel is live. You have an encrypted connection from your Windows machine through to your private VPC. Next up, you will launch a private EC2 instance and prove you can reach it through this tunnel.
Launch EC2 and Verify End-to-End Connectivity
Your VPN tunnel is live and your Windows machine has a 10.2.x.x address from the client CIDR. Traffic destined for the VPC now flows through the encrypted tunnel.
A tunnel that connects to nothing is just an expensive network adapter. You need a real workload in the private subnet to prove the VPN delivers actual access to resources that would otherwise be completely unreachable. In this step, you will launch a private EC2 instance and attempt to reach it through the tunnel.
In this step, get ready to:
- Create a security group and launch a private EC2 instance with no public IP.
- Test SSH access through the VPN tunnel and diagnose a failure.
- Fix the connectivity issue and verify end-to-end access.
Create a security group and launch a private EC2 instance
Every EC2 instance needs a security group that defines which traffic can reach it. You will create one that allows SSH on port 22 from your private subnet.
AWS CLI
- Create a security group in your VPN lab VPC by running this command:
aws ec2 create-security-group --group-name vpn-lab-sg --description "VPN lab target SG" --vpc-id [[VPC_ID="your VPC ID from Step 2"]]
The output returns a GroupId value (format: sg-xxxxxxxxxxxxxxxxx). Note this value for the next commands.
- Add an inbound rule allowing SSH from the subnet CIDR by running:
aws ec2 authorize-security-group-ingress --group-id [[SG_ID="your security group ID from above"]] --protocol tcp --port 22 --cidr 10.0.1.0/24
AWS Console
- In the AWS Console, navigate to the EC2 service.
- In the left sidebar, click Security Groups (under Network & Security).
- Click Create security group.
- In Security group name, enter vpn-lab-sg.
- In Description, enter VPN lab target SG.
- In the VPC dropdown, select vpn-lab-vpc.
- Under Inbound rules, click Add rule.
- Set Type to SSH.
- Set Source to Custom and enter 10.0.1.0/24.
- Click Create security group.
Note the Security group ID (format: sg-xxxxxxxxxxxxxxxxx) from the details page.
What do these commands do?
The first command creates a security group named vpn-lab-sg inside your VPC. Think of it as a firewall attached to your instance.
The second command opens port 22 (SSH) for traffic coming from 10.0.1.0/24, which is your private subnet's CIDR range.
aws ec2 run-instances --image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --instance-type t3.micro --subnet-id [[SUBNET_ID="your subnet ID from Step 2"]] --security-group-ids [[SG_ID="your security group ID"]] --key-name [[KEY_PAIR="your EC2 key pair name"]] --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=vpn-lab-target}]"
What does this command do?
The resolve:ssm: prefix tells AWS CLI to look up the latest Amazon Linux 2023 AMI ID automatically from Systems Manager Parameter Store. No need to hunt for AMI IDs yourself.
The instance launches in your private subnet with no public IP. The only way to reach it is through the VPN tunnel.
The output includes a PrivateIpAddress field (an IP in the 10.0.1.x range). Note this value.
Not seeing the instance launch?
If you get a NoKeyPair error, the key pair name you specified does not exist in this region. Run aws ec2 describe-key-pairs to list your available key pairs and use one that exists.
If you get a permissions error on the SSM parameter, your IAM user needs ssm:GetParameters permission to resolve the AMI ID.
help me troubleshoot my EC2 launch failure in the VPN lab
- Confirm the instance is running by executing:
aws ec2 describe-instances --filters "Name=tag:Name,Values=vpn-lab-target" --query "Reservations[0].Instances[0].[State.Name, PrivateIpAddress]" --output text
You should see running followed by the private IP address. If it shows pending, wait 30 seconds and run it again.
Attempt SSH through the tunnel
Your EC2 instance is running in the private subnet with no public IP. The only path to reach it is through your VPN tunnel.
Before you run the next command, predict what will happen. You are connected to the VPN with a 10.2.x.x address, and you are trying to SSH into a 10.0.1.x address. Do you think the connection will succeed?
- Open a new Command Prompt window (keep AWS VPN Client connected) and attempt SSH to your private instance:
ssh -i [[PEM_PATH="path to your .pem key file"]] ec2-user@[[PRIVATE_IP="the private IP of your EC2 instance"]]
The connection times out. No response at all.
Why does the SSH time out?
This failure is intentional. Look back at the security group rule you created: it allows SSH from 10.0.1.0/24 (the subnet CIDR).
Your VPN client has an IP in the 10.2.0.0/22 range (the client CIDR). The security group does not recognize your VPN traffic as coming from the subnet. It silently drops your packets.
This is the single most common Client VPN misconfiguration in production environments. Engineers allow traffic from the VPC subnet CIDR but forget that VPN clients get IPs from the Client CIDR, not the subnet CIDR.
Fix the security group and verify connectivity
The fix is straightforward: add inbound rules that permit traffic from the Client VPN CIDR range 10.2.0.0/22.
- Add an SSH rule allowing traffic from the VPN client CIDR by running:
aws ec2 authorize-security-group-ingress --group-id [[SG_ID="your security group ID"]] --protocol tcp --port 22 --cidr 10.2.0.0/22
- Add an ICMP rule for ping testing by running:
aws ec2 authorize-security-group-ingress --group-id [[SG_ID="your security group ID"]] --protocol icmp --port -1 --cidr 10.2.0.0/22
What do these rules do?
The first rule opens port 22 (SSH) to any IP in the 10.2.0.0/22 range, which is where all VPN client addresses live.
The second rule opens ICMP (the ping protocol) from the same range. This lets you confirm network connectivity before attempting SSH.
- Test network connectivity with ping:
ping [[PRIVATE_IP="the private IP of your EC2 instance"]]
You should see reply packets coming back from the private IP. Press Ctrl+C to stop the ping after a few replies.
Now for the real test. Do you think SSH will succeed this time?
- Attempt SSH again with the same command:
ssh -i [[PEM_PATH="path to your .pem key file"]] ec2-user@[[PRIVATE_IP="the private IP of your EC2 instance"]]
You are now logged into an EC2 instance that has no public IP address. The only path to this machine is through your encrypted VPN tunnel.
Confirm you are on the private instance
Run hostname -I inside the SSH session. You should see the 10.0.1.x private IP confirming you are on the right machine. Type exit to disconnect when you are done.
🙋♀️ SSH still timing out after adding the rules?
Verify your VPN is still connected. Check the AWS VPN Client status indicator. If it disconnected, reconnect and try again.
Confirm the security group rules were applied by running aws ec2 describe-security-groups --group-ids <your-sg-id> and checking that both the 10.2.0.0/22 rules appear in the output.
help me troubleshoot why SSH is still failing after updating my security group
You just proved end-to-end connectivity through an encrypted tunnel into a completely private instance. Next up, the Secret Mission challenges you to intentionally break this setup and diagnose the failures using CloudWatch logs.
Secret mission
Break It and Fix It - Troubleshooting Lab
In production, Client VPN issues manifest as silent timeouts and cryptic log entries. This mission builds the troubleshooting muscle memory you need when a 3am page comes in. You will intentionally break three critical VPN components, diagnose the failures using CloudWatch logs and CLI commands, and restore connectivity after each break.
Clean Up Your Resources
Clean Up Your Resources
This project uses AWS resources that incur hourly charges while active. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
Your Client VPN endpoint costs $0.10 per hour while a subnet is associated, plus $0.05 per hour for each active connection. Your EC2 instance (t3.micro) adds approximately $0.01 per hour. Leaving everything running overnight costs roughly $2.50. Delete or pause your resources as soon as you finish the project.
Resources you used:
- Client VPN endpoint (vpn-lab-endpoint) with subnet association.
- EC2 instance (vpn-lab-target) in the private subnet.
- VPC (vpn-lab-vpc), subnet (vpn-lab-private-subnet), and security group (vpn-lab-sg).
- CloudWatch log group (/aws/clientvpn).
- ACM certificates (server and client).
- Local certificate files in C:\vpn-certs and Easy-RSA in C:\EasyRSA-3.2.6.
- AWS VPN Client application.
Keep everything running
No action needed. Choose this if you plan to continue testing or extending the VPN within the next few hours.
- Your Client VPN endpoint and EC2 instance will continue to incur approximately $0.11 per hour combined.
- Disconnect your AWS VPN Client when you are not actively using the tunnel to save the $0.05/hr connection fee.
- Easy-RSA and the certificate files remain on your local machine at no cost.
Pause - I'll come back to this later
Shut down billable resources to stop ongoing charges, but keep configuration in place so you can resume later.
- Disconnect your AWS VPN Client session.
- Find your association ID by running:
aws ec2 describe-client-vpn-target-networks --client-vpn-endpoint-id <endpoint-id>
- Note the AssociationId from the output, then disassociate the subnet to stop the $0.10/hr charge:
aws ec2 disassociate-client-vpn-target-network --client-vpn-endpoint-id <endpoint-id> --association-id <association-id>
- Stop the EC2 instance to halt compute charges:
aws ec2 stop-instances --instance-ids <instance-id>
When you are ready to resume, re-associate the subnet, start the instance, and reconnect your VPN client.
Delete - I don't want to use this again
Remove all project resources completely. This is the recommended option to avoid any ongoing charges.
- Disconnect your AWS VPN Client session first.
- Terminate the EC2 instance:
aws ec2 terminate-instances --instance-ids <instance-id>
- Find your association ID:
aws ec2 describe-client-vpn-target-networks --client-vpn-endpoint-id <endpoint-id>
- Disassociate the target network:
aws ec2 disassociate-client-vpn-target-network --client-vpn-endpoint-id <endpoint-id> --association-id <association-id>
- Wait a few minutes for the disassociation to complete, then delete the Client VPN endpoint:
aws ec2 delete-client-vpn-endpoint --client-vpn-endpoint-id <endpoint-id>
- Delete the security group:
aws ec2 delete-security-group --group-id <sg-id>
- Delete the subnet:
aws ec2 delete-subnet --subnet-id <subnet-id>
- Delete the VPC:
aws ec2 delete-vpc --vpc-id <vpc-id>
- Delete both ACM certificates:
aws acm delete-certificate --certificate-arn <server-cert-arn>
aws acm delete-certificate --certificate-arn <client-cert-arn>
- Delete the CloudWatch log group:
aws logs delete-log-group --log-group-name /aws/clientvpn
- Delete local certificate files and Easy-RSA:
rmdir /s /q C:\vpn-certs
rmdir /s /q C:\EasyRSA-3.2.6
- Uninstall the AWS VPN Client by opening Windows Settings, selecting Apps, then Apps & features, finding AWS VPN Client, and clicking Uninstall.
That's a Wrap!
That's a Wrap!
Nice work! You just built a production-grade mutual TLS VPN from scratch and proved it works by tunneling into a completely private EC2 instance.
You've learned how to:
- Generate a full PKI certificate chain (CA, server, client) using Easy-RSA and import them into AWS Certificate Manager for mutual TLS authentication.
- Create and configure an AWS Client VPN endpoint with split-tunnel routing, subnet associations, authorization rules, and CloudWatch connection logging.
- Connect from your Windows machine through an encrypted tunnel and SSH into a private EC2 instance with no public IP, proving the VPN delivers real access to isolated infrastructure.
- Secret Mission: Intentionally broke the VPN in three different ways and diagnosed each failure using CloudWatch Logs Insights queries and CLI diagnostic commands before restoring full connectivity.
Ready to quiz yourself?