This CloudFormation template (+ optional Terraform module) helps you set up an AWS-managed VPN in about 10 minutes and operate it for as little as $1.41 per work day!
How this template minimizes costs:
-
Split-tunneling. Only AWS private network (VPC) traffic uses the VPN.
-
Single Availability Zone, by default. VPN clients can access VPC resources in any Availability Zone in the same region at no extra charge.
-
Optional on/off scheduling with github.com/sqlxpert/lights-off-aws .
Cost savings...
VPN usage Price (1 hour) Hours (7 days) Hours (365 days) Cost (365 days) Always on: Endpoint associated 10¢ 168 8,760 $876 1 client connected 5¢ 40 2,080 $104 Total $980 Work hours only: Endpoint associated 10¢ 50 2,607 $261 1 client connected 5¢ 40 2,080 $104 Total $365 $365 per year divided by 260 work days gives $1.41 per work day.
AWS Client VPN prices in the
us-east-1region were checked in October, 2025 but can change at any time. NAT gateway, data transfer, and other charges may also apply.
Rationale for connecting to AWS with a VPN...
Experts discourage relying on the strength of the perimeter around your private network, but sometimes, perimeter security is the available defense, and a virtual private network connection is necessary. For example, to access an AWS Elastic File System (EFS) volume from your local computer, you must use a VPN, so that the Network File System (NFS) client connection originates inside your AWS Virtual Private Cloud (VPC). NFS server software was not designed for exposure to the public Internet.
Before you begin, take a deep breath! Certificate setup is shorter than it looks. To avoid errors, read each step completely before doing it. You will have to switch between this ReadMe file and AWS's documentation.
AWS CloudShell might be useful for setup and maintenance, but be aware of limitations on persistent storage if you use your free CloudShell home directory to store your certificate authority (and Terraform state, if you are using Terraform).
-
Create the VPN certificate(s) by following AWS's mutual authentication steps.
-
Copy the individual Linux/macOS commands and execute them verbatim.
-
Copy and edit the block of commands before executing those. Not replacing
custom_folderis actually fine for now (if only AWS's technical writers had picked a meaningful folder name instead of a placeholder!), but after themkdirline, please insert:chmod go= ~/custom_folder -
After uploading the first (server) certificate, copy the ARN returned by AWS Certificate Manager.
-
Uploading the second (client) certificate is completely optional.
-
-
⚠ Tag the VPN certificate(s) if you are using Terraform. If you are not using a separate client certificate, apply both tags to the server certificate.
aws acm add-tags-to-certificate --tags 'Key=CVpnServer,Value=' --certificate-arn 'SERVER_CERT_ARN' aws acm add-tags-to-certificate --tags 'Key=CVpnClientRootChain,Value=' --certificate-arn 'CLIENT_CERT_ARN'
-
Install the Client VPN CloudFormation stack using CloudFormation or Terraform.
-
CloudFormation
Easy ✓Create a stack "With new resources (standard)" from a locally-saved copy of cloudformation/10-minute-aws-client-vpn.yaml [right-click to save as...].
-
Name the stack
CVpn. -
The parameters are thoroughly documented. Set only the "Essential" ones.
-
Under "Additional settings" → "Stack policy - optional", you can "Upload a file" and select a locally-saved copy of cloudformation/10-minute-aws-client-vpn-policy.json [right-click to save as...]. The stack policy prevents replacement or deletion of certain resources during stack updates, producing an error if you attempt parameter updates that are not possible.
-
-
Terraform
Check that you have at least:
Add the following child module to your existing root module:
module "cvpn" { source = "git::https://github.com/sqlxpert/10-minute-aws-client-vpn.git//terraform?ref=v4.1.1" # Reference a specific version from github.com/sqlxpert/10-minute-aws-client-vpn/releases cvpn_params = { TargetSubnetId = "subnet-10123456789abcdef" } }
Edit the subnet ID to match the ID of a subnet in the VPN's primary (or sole) Availability Zone.
Have Terraform download the module's source code. Review the plan before typing
yesto allow Terraform to proceed with applying the changes.terraform init terraform apply
⚠ Turn on the VPN by changing the
Enableparameter of theCVpnstack totruein CloudFormation. The Terraform module leaves the VPN off at first and then deliberately ignores changes tocvpn_params["Enable"]so that CloudFormation can manage that parameter.
-
-
Follow Step 7 of AWS's Getting Started document.
-
Find your VPN in the list of Client VPN endpoints in the AWS Console and download the configuration file from there.
-
cdto the directory where you downloaded the file and:chmod go= downloaded-client-config.ovpn
-
Open the file in your preferred editor, copy the skeleton from AWS's instructions and paste it at the end of the file, then replace the text between the tags with the contents of the
~/custom_folder/client1.domain.tld.crtcertificate file and the~/custom_folder/client1.domain.tld.keykey file. -
Rename
~/custom_folderand note that you must also continue to protecteasy-rsa/easyrsa3/pkianddownloaded-client-config.ovpn. All three contain copies of your key.
-
-
Download either the latest OpenVPN client (Resources → Connect Client → Download) or AWS client.
-
Import your edited configuration file to the client.
-
Use the client to connect to the VPN.
-
Add
FromClientSampleSecGrpto an EC2 instance or, if you do not use SSH, create and add a security group that accepts traffic from VPN clients on the port of your choice. -
Test. On your local computer, run:
ssh -i PRIVATE_KEY_FILE ec2-user@IP_ADDRESS
where PRIVATE_KEY_FILE is the path to the private key for the instance's SSH key pair, and IP_ADDRESS is the private address of the instance.
Different operating system images have different default usernames;
ec2-useris not always correct!If you do not use SSH, run a different command to test VPN connectivity.
-
Remove
FromClientSampleSecGrp(or equivalent) from you EC2 instance.
To turn the VPN on and off on a schedule...
-
If you used Terraform above, skip to Automatic Scheduling Step 2.
If you used CloudFormation...
-
Create a stack "With new resources (standard)" from a locally-saved copy of cloudformation/10-minute-aws-client-vpn-prereq.yaml [right-click to save as...].
-
Name this stack
CVpnPrereq. -
Under "Additional settings" → "Stack policy - optional", you can "Upload a file" and select a locally-saved copy of 10-minute-aws-client-vpn-prereq-policy.json [right-click to save as...]. The stack policy prevents inadvertent replacement or deletion of the deployment role during stack updates, but it cannot prevent deletion of the entire
CVpnPrereqstack. -
Update your initial
CVpnstack, changing nothing until the "Configure stack options" page, on which you will set "IAM role - optional" toCVpnPrereq-DeploymentRole. You are using a CloudFormation service role to delegate update privileges.If your own privileges are limited, you might need explicit permission to pass the role to CloudFormation. See the
CVpnPrereq-SampleDeploymentRolePassRolePolIAM policy for an example.
-
-
Update your
CVpnCloudFormation stack, adding the following stack-level tags:sched-set-Enable-true:u=1 u=2 u=3 u=4 u=5 H:M=11:00sched-set-Enable-false:u=2 u=3 u=4 u=5 u=6 H:M=01:00
In Terraform, set the following variable inside your
moduleblock:cvpn_schedule_tags = { sched-set-Enable-true = "u=1 u=2 u=3 u=4 u=5 H:M=11:00" sched-set-Enable-false = "u=2 u=3 u=4 u=5 u=6 H:M=01:00" }
Adjust the weekdays and the times based on your work schedule. This example is suitable for the mainland portions of the United States and Canada.
u=1is Monday andu=7is Sunday, per ISO 8601.- Times are in Universal Coordinated Time (UTC). This converter may be helpful: www.timeanddate.com .
- UTC has no provision for Daylight Saving Time/Summer Time. Leave a buffer at the end of your work day to avoid having to change schedules.
-
Find your VPN in the list of Client VPN endpoints in the AWS Console and check that its "Target network associations" are being created and deleted as scheduled. Check actual costs after a few days, and set up alerts with AWS Budgets.
You can toggle the Enable parameter (always in CloudFormation, never from
Terraform).
You can add or remove a backup subnet (in a second Availability Zone), but you must do it while the VPN is enabled, or the change won't register.
You can also switch between generic and custom VPN client security groups.
Do not try to change the VPC, the IP address ranges, or the path parameters
after the CVpn stack has been created. Instead, create a CVpn2 stack (in
Terraform, create a new module instance with
cvpn_stack_name_suffix = "2" ), then update the remote line of
your client configuration file and re-import the configuration file to your VPN
client utility.
| Output | Original Resource and Attribute |
|---|---|
| Matching Data Source and Argument | |
module.cvpn.cvpn_endpoint_id |
aws_ec2_client_vpn_endpoint.id |
data.aws_ec2_client_vpn_endpoint.client_vpn_endpoint_id |
|
module.cvpn.cvpn_client_sec_grp_id |
aws_security_group.id |
data.aws_security_group.id |
To accept traffic from VPN clients, reference module.cvpn.cvpn_client_sec_grp_id in:
aws_vpc_security_group..ingress.security_groupsoraws_vpc_security_group_ingress_rule.referenced_security_group_id
of server or listener security groups. ⚠ The security group output is not
available if cvpn_params["CustomClientSecGrpIds"] was set.
To automate certificate creation, consider third-party modules such as:
If you run Terraform with least-privilege permissions...
If you do not give Terraform full AWS administrative permissions, you must give it permission to:
-
List, describe, get tags for, create, tag, update, untag and delete IAM roles, update the "assume role" (role trust or "resource-based") policy, and put and delete in-line policies
-
List, describe, create, tag, update, untag, and delete CloudFormation stacks
-
Set and get CloudFormation stack policies
-
Pass
CVpnPrereq-DeploymentRole-*to CloudFormation -
List, describe, and get tags for, all
datasources. For a list, run:grep 'data "' terraform*/*.tf | cut --delimiter=' ' --fields='1,2'
Open the AWS Service Authorization Reference, go through the list of services on the left, and consult the "Actions" table for each of:
AWS Identity and Access Management (IAM)CloudFormationAWS Security Token ServiceAmazon EC2AWS Certificate ManagerAWS Systems ManagerAWS Key Management Service(if you encrypt the CloudWatch log group with a KMS key)
In most cases, you can scope Terraform's permissions to one workload by regulating resource naming and tagging, and then by using:
- ARN patterns in
Resourcelists - ARN patterns in
Conditionentries - Request tag and then resource tag
Conditionentries
Check Service and Resource Control Policies (SCPs and RCPs), as well as resource policies (such as KMS key policies).
The deployment role defined in the CVpnPrereq stack gives CloudFormation the
permissions it needs to create the CVpn stack. Terraform itself does not need
the deployment role's permissions.
To help improve the 10-minute AWS Client VPN template, please report bugs and propose changes.
| Scope | Link | Included Copy |
|---|---|---|
| Source code files, and source code embedded in documentation files | GNU General Public License (GPL) 3.0 | LICENSE-CODE.md |
| Documentation files (including this readme file) | GNU Free Documentation License (FDL) 1.3 | LICENSE-DOC.md |
Copyright Paul Marcelin
Contact: marcelin at cmu.edu (replace "at" with @)