---
title: 'Tutorial: Deploy Access Server on AWS with Terraform'
canonical: https://openvpn.net/as-docs/v3/tutorials/tutorial--deploy-access-server-aws-terraform/
product: Access Server Docs
description: Deploy OpenVPN Access Server on AWS using the official Terraform configuration — automates EC2 provisioning on Ubuntu 24.04 LTS with a static Elastic IP, Let's Encrypt SSL certificate, and cloud-init setup. Includes tear-down with terraform destroy.
---

# Tutorial: Deploy Access Server on AWS with Terraform

## Overview

This tutorial shows you how to deploy OpenVPN Access Server on AWS with the official [OpenVPN/openvpn-as-aws-terraform-deploy](https://github.com/OpenVPN/openvpn-as-aws-terraform-deploy) Terraform configuration.

The Terraform configuration launches an Amazon EC2 instance in an existing VPC and subnet and uses cloud-init during first boot to install and configure Access Server. You provide the AWS networking resources, EC2 key pair, and Access Server administrative credentials.

The default EC2 instance type is `t3.small`. You can change the instance size with the `aws_instance_type` Terraform variable.

By default, the deployment also:

- Allocates a static **Elastic IP** by setting `use_static_public_ip` to `true`.
- Requests a trusted Let’s Encrypt TLS certificate by setting `enable_lets_encrypt` to `true`.
- Creates the security group required for Access Server.
- Returns the Admin Web UI and Client Web UI URLs after deployment.

> **Tip**
>
> Access Server runs `ovpn-init` on first boot to complete its initial configuration. This is expected and can take up to five minutes.

For other AWS deployment and licensing options, refer to Deploy [Access Server on AWS — Choose Your Licensing Model](https://openvpn.net/as-docs/v3/aws.md).

### Use cases

| **Audience** | **Use case** |
| --- | --- |
| Developers | Create repeatable Access Server test environments and tear them down on demand with `terraform destroy`. |
| IT administrators and DevOps engineers | Provision Access Server and its supporting AWS infrastructure consistently across environments. |
| Organizations | Manage Access Server infrastructure as version-controlled, reviewable code. |

### Where you can run Terraform

You can run this Terraform configuration from:

- **Your workstation:** Run Terraform CLI on Windows, macOS, or Linux.
- **On-premises infrastructure:** Run Terraform from your own servers, CI/CD pipelines, or virtualization infrastructure.
- **Cloud automation:** Run the configuration from HCP Terraform or another CI/CD pipeline with access to your AWS account.

Refer to the [Access Server system requirements](https://openvpn.net/as-docs/v3/system-requirements.md) when selecting an EC2 instance type for your expected workload.

## Prerequisites

Before you begin, ensure you have:

- An AWS account with permissions to create and manage EC2 instances, security groups, and Elastic IP addresses, and permissions to read existing VPC, subnet, and EC2 key pair information.
- AWS CLI credentials, such as an access key ID and secret access key, or another supported authentication method.
- [Terraform](https://developer.hashicorp.com/terraform/install) installed on your workstation.
- The AWS CLI installed.
- Git installed.
- An existing VPC and public subnet. The subnet needs a route to an internet gateway so the instance can reach the internet and VPN clients can reach Access Server.
- An EC2 key pair in the AWS Region where you plan to deploy Access Server.

> **Note**
>
> In our documentation, we use example IPv4 addresses and subnets [reserved for documentation](https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv4), such as `192.0.2.0/24`, `198.51.100.0/24`, and `203.0.113.0/24`.
>
> Ensure you replace them with valid IPv4 addresses and subnets for your network(s).

## What the Terraform configuration creates

| **Resource** | **Details** |
| --- | --- |
| EC2 instance | Amazon EC2 instance for Access Server. The default instance type is `t3.small`. |
| Access Server | Access Server installed during first boot through `cloud-init`. The configuration runs `apt-mark hold openvpn-as`, so normal package upgrades don't automatically update Access Server. Upgrade Access Server separately when you want to move to a newer version. |
| Security group | `AccessServerSecurityGroup`, attached to the instance with the required inbound rules. |
| Elastic IP | Static public IP address when `use_static_public_ip` is `true`, which is the default. |
| Web SSL certificate | Let’s Encrypt certificate requested during first boot when `enable_lets_encrypt` is `true`, which is the default. |

## Network ports

The Terraform security group allows the following inbound traffic. All outbound traffic is allowed.

| **Port** | **Protocol** | **Purpose** |
| --- | --- | --- |
| 943 | TCP | Admin Web UI and Client Web UI |
| 443 | TCP | Web interfaces and OpenVPN TCP connections |
| 1194 | UDP | OpenVPN UDP connections |
| 22 | TCP | SSH administration |

> **Important**
>
> By default, SSH access on TCP port 22 is allowed from `0.0.0.0/0`. Step 4 shows how to restrict SSH access before deployment.

## Step 1: Clone the official Terraform configuration

Clone the `OpenVPN/openvpn-as-aws-terraform-deploy` repository to the workstation where you'll run Terraform.

1. Open a terminal or PowerShell on your workstation.
2. Clone the repository:

   ```
   git clone https://github.com/OpenVPN/openvpn-as-aws-terraform-deploy.git
   ```

   - Example output:

     ```
     Cloning into 'openvpn-as-aws-terraform-deploy'...
     ```
3. Change to the repository directory:

   ```
   cd openvpn-as-aws-terraform-deploy
   ```
4. Verify that the repository contains the Terraform configuration files. The repository includes:

   | **File** | **Purpose** |
   | --- | --- |
   | `README.md` | Project documentation. |
   | `ami.tf` | Base image configuration. |
   | `eip.tf` | Static Elastic IP configuration when `use_static_public_ip` is `true`. |
   | `outputs.tf` | Terraform outputs, including the Admin Web UI and Client Web UI URLs. |
   | `provider.tf` | AWS provider configuration. |
   | `security_groups.tf` | Defines the Access Server security group and its inbound and outbound rules. |
   | `user_data.tf` | Defines the EC2 instance, including the AMI, instance type, subnet, and key pair, and the user data that installs and configures Access Server during first boot. |
   | `variables.tf` | Terraform input variables. |

## Step 2: Configure your AWS credentials

Terraform uses your AWS credentials to create and manage the resources for the Access Server deployment.

1. If you haven't configured credentials for the AWS CLI, run:

   ```
   aws configure
   ```
2. Enter your access key ID, secret access key, and default AWS Region when prompted.
3. Verify that the credentials work:

   ```
   aws sts get-caller-identity
   ```

   - The command returns your AWS account ID and user or role ARN.
   - If the command returns an authentication or authorization error, correct your AWS credentials before continuing.

## Step 3: Collect your VPC, subnet, and key pair details

You need the VPC ID, public subnet ID, and EC2 key pair name for the AWS Region where you'll deploy Access Server.

The following examples use `us-east-1`. Replace the Region if needed.

> **Tip**
>
> If you already have these values, proceed to Step 4.

1. Find your VPC ID:
   - On Windows PowerShell:

     ```
     aws ec2 describe-vpcs --region us-east-1 `
       --query "Vpcs[].[VpcId,CidrBlock]" --output table
     ```
   - On macOS or Linux:

     ```
     aws ec2 describe-vpcs --region us-east-1 \
       --query "Vpcs[].[VpcId,CidrBlock]" --output table
     ```
   - Record the VPC ID you want to use.
2. Find a subnet in that VPC. Replace `vpc-xxxxxxxx` with your VPC ID:
   - On Windows PowerShell:

     ```
     aws ec2 describe-subnets --region us-east-1 `
       --filters "Name=vpc-id,Values=vpc-xxxxxxxx" `
       --query "Subnets[].[SubnetId,AvailabilityZone,CidrBlock]" `
       --output table
     ```
   - On macOS or Linux:

     ```
     aws ec2 describe-subnets --region us-east-1 \
       --filters "Name=vpc-id,Values=vpc-xxxxxxxx" \
       --query "Subnets[].[SubnetId,AvailabilityZone,CidrBlock]" \
       --output table
     ```
   - Choose a public subnet whose route table sends `0.0.0.0/0` to an internet gateway, and record its subnet ID.
3. Find the EC2 key pairs available in the Region:
   - On Windows PowerShell:

     ```
     aws ec2 describe-key-pairs --region us-east-1 `
       --query "KeyPairs[].KeyName" `
       --output table
     ```
   - On macOS or Linux:

     ```
     aws ec2 describe-key-pairs --region us-east-1 \
       --query "KeyPairs[].KeyName" \
       --output table
     ```
4. Record the key pair name you want to use for SSH access.

   > **Tip**
   >
   > The EC2 key pair needs to exist in the same AWS Region as the Access Server deployment. A key pair created in another Region won't appear in the results.

## Step 4: Set the Terraform input variables

Configure the Terraform variables for your AWS environment and Access Server deployment.

The configuration uses these primary variables:

| **Variable** | **Default** | **Description** |
| --- | --- | --- |
| `aws_region` | `us-east-1` | AWS Region where Terraform deploys the resources. Change it if you're deploying in another Region. |
| `aws_instance_type` | `t3.small` | EC2 instance type for Access Server. |
| `vpc_id` | Placeholder — replace with your value | ID of your existing VPC. |
| `subnet_id` | Placeholder — replace with your value | ID of your existing subnet. |
| `key_name` | Placeholder — replace with your value | EC2 key pair used for SSH access. |
| `admin_username` | `admin1` — replace with your value | Access Server administrative username. |
| `admin_password` | `p@$w0rd` — replace before deployment | Access Server administrative password. Don't deploy with the default value. |
| `enable_lets_encrypt` | `true` | Requests a Let's Encrypt TLS certificate during first boot. |
| `use_static_public_ip` | `true` | Allocates a static Elastic IP instead of a dynamic auto-assigned public IP. |

1. Open `variables.tf` in a text editor.
2. Set aws\_region to the AWS Region containing the resources you identified in Step 3. For example:

   ```
   variable "aws_region" {
     description = "AWS region to deploy the resources in"
     type        = string
     default     = "us-east-1" # Replace with your desired AWS region
   }
   ```

   - To deploy in `us-west-1`, change the value:

     ```
     default = "us-west-1"
     ```
3. Set `vpc_id` to your VPC ID.
4. Set `subnet_id` to your public subnet ID.
5. Set `key_name` to the EC2 key pair name you want to use for SSH access.
6. Set `admin_username` to the administrative username you want to use for Access Server.
7. Set `admin_password` to a strong, unique password before deploying.

   > **Important**
   >
   > The repository includes `p@$w0rd` as a placeholder default. Don't deploy Access Server with this password.

   > **Important**
   >
   > Treat the Access Server administrative password and Terraform deployment files as sensitive. Regardless of how you provide the password, the deployment stores it in plain text in the Terraform state and in the EC2 instance user data. Users with permission to read the instance user data can retrieve it. The Terraform output is also not marked sensitive, so `terraform apply` and `terraform output` display the password.
   >
   > Don't commit passwords or Terraform state files to a shared or public source repository. Limit access to the Terraform state and EC2 instance configuration, and change the Access Server administrative password after your first successful authentication.
8. Review `aws_instance_type`. The default instance type is `t3.small`. Change it to a different EC2 instance type if it better fits your expected workload.
9. Leave `use_static_public_ip` set to `true` to allocate a static Elastic IP.

   > **Note**
   >
   > The Elastic IP stays associated with the Access Server instance, rather than relying on a dynamic, auto-assigned public IP that can change.
10. Leave `enable_lets_encrypt` set to `true` to request a Let's Encrypt TLS certificate during first boot.
11. (Optional) Set `TF_VAR_admin_password` to provide the administrative password rather than storing it as a Terraform input variable:
    - On Windows PowerShell:

      ```
      $env:TF_VAR_admin_password = 'YourStrongPassword'
      ```
    - On macOS or Linux:

      ```
      export TF_VAR_admin_password='YourStrongPassword'
      ```

      > **Tip**
      >
      > Using `TF_VAR_admin_password` keeps the password out of `variables.tf`.

    > **Important**
    >
    > Use a strong, unique password. The Terraform deployment also returns the configured administrative credentials in its output, so treat the output as sensitive. Change the administrative password in the Admin Web UI after your first successful authentication.
12. (Recommended) Restrict SSH access using an IP allowlist. Open `security_groups.tf`.
13. Locate the ingress rule for TCP port 22.
14. Replace `0.0.0.0/0` with your public IP address or a trusted network in CIDR notation.
    - For example, to allow SSH from one IPv4 address:

      ```
      ingress {
        from_port   = 22
        to_port     = 22
        protocol    = "tcp"
        cidr_blocks = ["203.0.113.10/32"]
      }
      ```
    - To allow a trusted subnet, specify its CIDR instead, such as:

      ```
      203.0.113.0/24
      ```
15. Save your changes.

## Step 5: Deploy Access Server with Terraform

Initialize Terraform, review the planned AWS resources, and apply the configuration.

1. From the `openvpn-as-aws-terraform-deploy` repository directory, initialize the Terraform working directory:

   ```
   terraform init
   ```

   - Terraform downloads the required AWS provider.
2. If you're using `TF_VAR_admin_password`, ensure the environment variable is set in the current terminal session:
   - On Windows PowerShell:

     ```
     if ($env:TF_VAR_admin_password) { "TF_VAR_admin_password is set" }
     ```
   - On macOS or Linux:

     ```
     if [ -n "$TF_VAR_admin_password" ]; then echo "TF_VAR_admin_password is set"; fi
     ```
   - Ensure the command states that the password is set.
3. Preview the resources Terraform plans to create:

   ```
   terraform plan
   ```
4. Review the plan and confirm that it includes the expected EC2 instance, security group, and Elastic IP (if enabled).
5. Apply the Terraform configuration:

   ```
   terraform apply
   ```
6. Review the plan again when prompted.
7. Enter `yes` to confirm.
   - Terraform creates the AWS resources and begins the Access Server first-boot configuration.
8. Review the Terraform outputs after the deployment finishes. The deployment returns:

   | **Output** | **Description** |
   | --- | --- |
   | `admin_ui_url` | Admin Web UI URL, such as `https://203.0.113.25:943/admin`. |
   | `client_ui_url` | Client Web UI URL, such as `https://203.0.113.25:943`. |
   | `openvpnas_user` | The Access Server administrative credentials you configured. |
9. To display the outputs again later, run:

   ```
   terraform output
   ```
10. Allow Access Server to finish its first-boot initialization before attempting to authenticate.

    > **Important**
    >
    > Access Server isn't immediately ready when `terraform apply` finishes.
    >
    > During first boot, `cloud-init` runs the installation process, and Access Server runs `ovpn-init` to complete its configuration. This is expected and can take up to five minutes.
    >
    > Authentication attempts can fail while initialization is still in progress. Wait for initialization to finish before attempting to sign in.

## Step 6: Sign in and verify the Access Server deployment

After initialization finishes, use the Terraform outputs to access Access Server and verify the deployment.

1. Open the URL returned in `admin_ui_url`. For example:

   ```
   https://203.0.113.25:943/admin
   ```
2. Sign in with the administrative username and password you configured.
3. Review and accept the end-user license agreement (EULA).
4. Verify the web certificate.

   > **Tip**
   >
   > When `enable_lets_encrypt` is `true`, the deployment requests a Let's Encrypt certificate for the Access Server public IP address during first boot.
   >
   > After the certificate is issued, your browser should establish a trusted HTTPS connection without showing the warning associated with the default self-signed certificate.
   >
   > If you turn off Let's Encrypt, Access Server uses its self-signed certificate.
   >
   > For more information about certificate configuration, refer to [Tutorial: How to Use ACME Client to Issue Web SSL Certificates for the Access Server UI](https://openvpn.net/as-docs/v3/tutorials/tutorial--acme-web-ssl-certificates.md)
5. Verify that the Admin Web UI loads successfully.
6. Verify that the Client Web UI is available at the URL returned in `client_ui_url`.
7. If `use_static_public_ip` is `true`, verify that the EC2 instance uses the Elastic IP created by Terraform.
8. Continue with [Tutorial: Get Started with Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--access-server-install-and-set-up.md) to complete your Access Server configuration and establish your first VPN connection.
   - Users can open the `client_ui_url`, sign in, and download OpenVPN Connect with their connection profile.

## Step 7: Connect to the EC2 instance with SSH (optional)

Use SSH when you need console access for administration or troubleshooting.

The default username for Ubuntu AMIs on AWS is `ubuntu`, so the SSH destination uses the `ubuntu@` format.

1. Ensure you have the private key that matches the EC2 key pair configured in Step 4.
2. Identify the public IP address assigned to the Access Server EC2 instance. If `use_static_public_ip` is `true`, use the Elastic IP.
3. Connect to the instance with SSH:

   ```
   ssh -i /path/key-pair-name.pem ubuntu@instance-public-ipv4-address
   ```

   Replace `/path/key-pair-name.pem` with the path to your private key.

   Replace `instance-public-ipv4-address` with the Access Server public IP address.

## Remove the deployment

When you no longer need the Access Server environment, use `terraform destroy` to remove the resources created by the Terraform configuration.

If you want to retain the Elastic IP, remove it from Terraform management before destroying the remaining resources.

1. Open a terminal or PowerShell.
2. Change to the `openvpn-as-aws-terraform-deploy` repository directory.
3. If you want to keep the Elastic IP, remove it from the Terraform state:

   ```
   terraform state rm aws_eip.AccessServerEIP
   ```

   - This removes the Elastic IP from Terraform management without releasing the address in AWS.
4. Review the resources Terraform plans to remove:

   ```
   terraform plan -destroy
   ```
5. Destroy the remaining resources:

   ```
   terraform destroy
   ```
6. Review the destroy plan, then enter `yes`.

> **Important**
>
> `terraform destroy` permanently deletes the Access Server instance and its configuration, including users and settings.
>
> Back up any data you need before destroying the environment.
>
> If you want to retain the Elastic IP, remove it from Terraform management before running `terraform destroy`.

## Related resources

- [Deploy Access Server on AWS — Choose Your Licensing Model](https://openvpn.net/as-docs/v3/aws.md)
- [Tutorial: Get Started with Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--access-server-install-and-set-up.md)
- [Tutorial: How to Use ACME Client to Issue Web SSL Certificates for the Access Server UI](https://openvpn.net/as-docs/v3/tutorials/tutorial--acme-web-ssl-certificates.md)
- [Tutorial: Troubleshoot and Optimize VPN Performance in Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--vpn-performance-troubleshooting.md)
- [APIs & Automation tutorials](https://openvpn.net/as-docs/v3/tutorials/apis-and-automation.md)

---

Prev: [Automation Tools](https://openvpn.net/as-docs/v3/tutorials/automation-tools.md)
Next: [Tutorial: Connect Your AI Agent to Access Server Using the MCP Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--access-server-mcp-server.md)
Index: https://openvpn.net/as-docs/llms.txt
