WireGuard Single-Instance VPN: AWS EC2 Instance to MikroTik hEX at Home

In a previous post, I configured a WireGuard site-to-site VPN between my MikroTik hEX router at home and OPNsense 25.7 running on AWS:

That setup was designed to connect two networks:

Homelab LAN <-> AWS VPC subnet

For this post, I have a different requirement.

I have another AWS environment where I only need one EC2 instance to access my homelab network. I do not need to connect the entire AWS VPC to my home network.

Before configuring the VPN, I also created a custom VPC in the AWS Asia Pacific (Taipei) Region:

That custom VPC uses:

Region:   ap-east-2
VPC CIDR: 10.20.0.0/16

Now I will launch an EC2 instance in that VPC and configure WireGuard directly on the EC2 instance.

This is a single-instance VPN:

One EC2 instance -> WireGuard -> MikroTik hEX at home

It is not a full AWS VPC-to-homelab site-to-site VPN.


Goal

The goal is to allow one EC2 instance in AWS to access my homelab network through WireGuard.

The EC2 instance should be able to access internal home resources such as:

192.168.0.1      MikroTik router
192.168.0.x      Homelab servers

I also want to access the EC2 instance from home through its WireGuard IP.

For example:

ssh ubuntu@100.97.0.2

Only this EC2 instance will use the VPN.

Other AWS resources in the same VPC will not automatically route through this VPN.


Related Posts

This post builds on these earlier posts.

First, I configured a full site-to-site VPN between my homelab and an AWS network:

Then I created a custom VPC in the AWS Asia Pacific (Taipei) Region:

I also documented a separate issue I hit when running the WireGuard wg command inside an AWS SSM Session Manager session:

This post focuses only on the EC2-to-home WireGuard VPN configuration.


Why Single-Instance VPN Instead of Site-to-Site?

In this AWS environment, I only need one EC2 instance.

I do not have or need:

OPNsense
Transit Gateway
NAT instance
VPN gateway
Router instance
Full VPC-to-homelab routing

A site-to-site VPN is useful when I want to connect entire networks, such as:

Homelab LAN <-> AWS VPC subnet

But in this case, I only want:

One EC2 instance <-> Homelab LAN

So the simpler design is:

EC2 instance
  |
  | WireGuard
  |
MikroTik hEX at home
  |
Homelab LAN

This design is simpler because:

No AWS route table change is required.
No EC2 source/destination check change is required.
No AWS router instance is required.
No Transit Gateway is required.
No VPC peering is required.
No public SSH inbound rule is required on the EC2 security group.
No inbound WireGuard rule is required on the EC2 security group.

The EC2 instance itself runs WireGuard, and the WireGuard client route sends only homelab traffic through the VPN.


Existing Site-to-Site VPN Will Stay Unchanged

I already have another WireGuard site-to-site VPN between my home MikroTik hEX and an OPNsense firewall running on AWS.

That previous setup used a separate WireGuard tunnel network:

Existing site-to-site VPN: 100.96.0.0/24

I will not modify that existing VPN.

For this EC2 single-instance VPN, I will use a separate WireGuard interface, port, and tunnel subnet:

New EC2 VPN: 100.97.0.0/24

This keeps the single-instance VPN separate from the existing site-to-site VPN.


Network Plan

In this post, the environment is:

AWS Region:              ap-east-2
AWS VPC CIDR:            10.20.0.0/16
AWS public subnets:      10.20.0.0/20
                         10.20.16.0/20
                         10.20.32.0/20

EC2 instance name:       app-tpe
EC2 instance ID:         i-0dde55e7533e0cf46
EC2 instance type:       t4g.micro
EC2 subnet:              ap-east-2b / 10.20.16.0/20
EC2 private IPv4:        10.20.30.151
EC2 public IPv4:         43.212.14.245
EC2 public DNS:          ec2-43-212-14-245.ap-east-2.compute.amazonaws.com
Elastic IP:              none
IAM role:                none
IMDSv2:                  required

Homelab LAN:             192.168.0.0/24
MikroTik LAN IP:         192.168.0.1
MikroTik public DNS:     home.maksonlee.com

WireGuard subnet:        100.97.0.0/24
MikroTik WireGuard IP:   100.97.0.1
EC2 WireGuard IP:        100.97.0.2

MikroTik interface:      wg-ec2
WireGuard UDP port:      51821

The traffic path from EC2 to home will be:

EC2 100.97.0.2
  -> WireGuard tunnel
  -> MikroTik 100.97.0.1
  -> Homelab LAN 192.168.0.0/24

The traffic path from home to EC2 will be:

Homelab PC 192.168.0.x
  -> MikroTik
  -> WireGuard tunnel
  -> EC2 100.97.0.2

EC2 Placement

The EC2 instance is launched in the custom VPC created in the previous post:

VPC: 10.20.0.0/16

In this example, the instance is placed in the public subnet in ap-east-2b:

Subnet:     10.20.16.0/20
Private IP: 10.20.30.151

The EC2 instance needs Internet access so it can initiate the WireGuard connection to my home MikroTik router.

For this simple setup, I use a public subnet and enable public IPv4 assignment.


Accessing the EC2 Instance Without Opening SSH

For this setup, I do not open SSH port 22 from the Internet on the EC2 security group.

I keep the EC2 inbound rules unchanged and use AWS Systems Manager Session Manager instead.

In this account and Region, I only enabled Default Host Management Configuration, also called DHMC:

Systems Manager -> Fleet Manager -> Default Host Management Configuration -> Enable

After enabling DHMC and waiting a short time, the EC2 instance became available in Session Manager.

Then I could connect from:

EC2 -> Instances -> app-tpe -> Connect -> Session Manager

With this setup, I do not need:

Public SSH access
Inbound TCP 22 from the Internet
A bastion host
A manually attached IAM instance profile

The EC2 security group can remain simple:

Inbound:
No public inbound rules

Outbound:
Allow all

The default security group may still contain its default self-referencing inbound rule, where the source is the same security group. That is not public Internet access. I do not add SSH 22 or WireGuard UDP 51821 from 0.0.0.0/0.

This works because Session Manager access is initiated through the SSM Agent on the instance, and the instance only needs outbound connectivity to AWS Systems Manager.

This is useful for the WireGuard setup because I can install and configure WireGuard without exposing SSH to the Internet.

For WireGuard itself, the EC2 instance also initiates the VPN connection outbound:

EC2 -> home.maksonlee.com:51821

So the EC2 security group still does not need an inbound WireGuard rule.

The only inbound WireGuard rule is on the MikroTik side.


Important Note About Public IP

This design assumes the EC2 instance can reach the MikroTik router from the Internet.

That means the MikroTik router needs one of these:

A public IPv4 address
A working DDNS name
A port-forward from the ISP router to MikroTik

In this setup, I use:

home.maksonlee.com:51821

If the home Internet connection is behind CGNAT, this direct inbound design will not work.

In that case, the design should be reversed: the EC2 instance should become the WireGuard listener, and MikroTik should initiate the connection outbound.


Do I Need an Elastic IP on EC2?

No, an Elastic IP is not required for WireGuard to work in this design.

The EC2 instance initiates the WireGuard connection to MikroTik.

So MikroTik does not need to know the EC2 public IP in advance.

However, an Elastic IP is useful if I want to restrict the MikroTik firewall rule to only allow WireGuard packets from that EC2 public IP.

Without Elastic IP:

MikroTik allows UDP 51821 from the Internet.
WireGuard authentication still protects the tunnel.

With Elastic IP:

MikroTik allows UDP 51821 only from the EC2 Elastic IP.
This is cleaner from a firewall perspective.

For testing, Elastic IP is optional.

For a stricter firewall setup, I can attach an Elastic IP and restrict the MikroTik rule to that Elastic IP. In this lab setup, I do not use an Elastic IP because I want to keep the setup simple and avoid extra public IP management.

In this post, the EC2 instance uses an auto-assigned public IPv4 address, so I do not restrict the MikroTik firewall rule to a specific EC2 public IP.

The current public IPv4 address is:

43.212.14.245

But this public IPv4 address may change after stop/start because it is not an Elastic IP.


Do I Need to Open an Inbound Port on EC2?

No.

In this design, the EC2 instance initiates the WireGuard connection to MikroTik.

The direction is:

EC2 -> home.maksonlee.com:51821

So the EC2 security group does not need an inbound WireGuard rule.

The EC2 security group only needs outbound access:

Protocol: UDP
Destination: home.maksonlee.com
Port: 51821

If the EC2 security group uses the default outbound allow-all rule, no security group change is needed for WireGuard.

The inbound WireGuard firewall rule is needed on MikroTik, not on EC2.


  1. Create a New WireGuard Interface on MikroTik

On MikroTik, create a new WireGuard interface.

I do not reuse the existing WireGuard interface from the previous site-to-site VPN.

/interface wireguard
add name=wg-ec2 listen-port=51821 mtu=1420

Assign the MikroTik WireGuard IP:

/ip address
add address=100.97.0.1/24 interface=wg-ec2

Check the WireGuard interface:

/interface wireguard print detail

Example output:

Flags: X - DISABLED; R - RUNNING
 0  R name="wg-aws" mtu=1420 listen-port=51820 public-key="7z7jpkNF02wXxH5ztdfrclbPYl5i5WUaHubXHU8RMSk="

 1  R name="wg-ec2" mtu=1420 listen-port=51821 public-key="H2MT9wlUE+UBn9qMEkilYChWAlHp9Q9YBYfX5yJcWy4="

The public key of wg-ec2 will be used in the EC2 WireGuard configuration.


  1. Check the Existing MikroTik Firewall Rules

Before adding new firewall rules, I first check the current MikroTik firewall rules.

This is important because MikroTik firewall rules are order-sensitive.

Run:

/ip firewall filter print

Also check the interface lists:

/interface list print
/interface list member print

In my case, the important firewall rules were:

5    ;;; defconf: drop all not coming from LAN
      chain=input action=drop in-interface-list=!LAN

11   ;;; defconf: drop all from WAN not DSTNATed
      chain=forward action=drop connection-state=new connection-nat-state=!dstnat in-interface-list=WAN

The interface lists were:

LAN   bridge
WAN   ether1
WAN   pppoe-out1

The new wg-ec2 interface is not in the LAN list and not in the WAN list.

This means the input chain is important.

The default input drop rule blocks traffic that does not come from the LAN list:

chain=input action=drop in-interface-list=!LAN

So if I simply add the WireGuard rule at the end, it will not work.

The WireGuard allow rule must be placed before that input drop rule.


  1. Allow WireGuard on the MikroTik Firewall

Allow UDP port 51821 on MikroTik.

Because this EC2 instance does not use an Elastic IP, its public IPv4 address may change after stop/start.

So for this setup, I do not restrict the MikroTik firewall rule to the EC2 public IP.

I insert the rule before the default input drop rule:

/ip firewall filter
add chain=input action=accept in-interface-list=WAN protocol=udp dst-port=51821 comment="Allow WireGuard from EC2" place-before=[find where comment="defconf: drop all not coming from LAN"]

This allows the EC2 instance to initiate the WireGuard handshake to MikroTik.

If I later attach an Elastic IP to the EC2 instance, I can make the rule stricter:

/ip firewall filter
add chain=input action=accept in-interface-list=WAN protocol=udp src-address=EC2_ELASTIC_IP dst-port=51821 comment="Allow WireGuard from EC2 Elastic IP" place-before=[find where comment="defconf: drop all not coming from LAN"]

For the current setup, I use the version without src-address.


  1. Allow VPN Traffic on MikroTik

After the tunnel is established, I also want to allow the EC2 WireGuard peer to access the MikroTik router itself.

This is input-chain traffic.

Examples:

ping 100.97.0.1
ping 192.168.0.1

Because wg-ec2 is not in the LAN interface list, I insert this rule before the default input drop rule:

/ip firewall filter
add chain=input action=accept in-interface=wg-ec2 src-address=100.97.0.2 comment="Allow EC2 VPN to MikroTik" place-before=[find where comment="defconf: drop all not coming from LAN"]

Then I allow the EC2 instance to access the homelab LAN.

This is forward-chain traffic:

/ip firewall filter
add chain=forward action=accept in-interface=wg-ec2 dst-address=192.168.0.0/24 comment="Allow EC2 VPN to homelab LAN" place-before=[find where comment="defconf: drop all from WAN not DSTNATed"]

I also allow the homelab LAN to access the EC2 WireGuard IP:

/ip firewall filter
add chain=forward action=accept src-address=192.168.0.0/24 dst-address=100.97.0.2 out-interface=wg-ec2 comment="Allow homelab LAN to EC2 VPN IP" place-before=[find where comment="defconf: drop all from WAN not DSTNATed"]

This last rule is important if I want to SSH from home to the EC2 instance using:

ssh ubuntu@100.97.0.2

I use explicit firewall rules instead of adding wg-ec2 to the LAN interface list.

That keeps the VPN permissions more specific.

After adding the rules, I check the firewall again:

/ip firewall filter print

The important result is that the WireGuard input rules are above the default input drop rule:

Allow WireGuard from EC2
Allow EC2 VPN to MikroTik
defconf: drop all not coming from LAN

And the forward rules are above the default WAN forward drop rule:

Allow EC2 VPN to homelab LAN
Allow homelab LAN to EC2 VPN IP
defconf: drop all from WAN not DSTNATed

  1. Install WireGuard on the EC2 Instance

Using Session Manager, connect to the EC2 instance.

Then install WireGuard:

sudo apt update
sudo apt install -y wireguard wireguard-tools

In this setup, I use AWS Systems Manager Session Manager instead of opening public SSH.

On Ubuntu 26.04, I previously hit an issue where the wg command behaved unexpectedly inside an SSM Session Manager session. I documented that issue separately here:

For this post, I keep the WireGuard setup simple and use the path allowed by the WireGuard AppArmor profile:

/etc/wireguard/

Do not generate the key files under /tmp in this SSM Session Manager environment.

Create the WireGuard directory:

sudo install -d -m 700 /etc/wireguard

Generate the EC2 WireGuard key pair under /etc/wireguard:

sudo sh -c 'umask 077; wg genkey > /etc/wireguard/ec2_private.key'
sudo sh -c 'wg pubkey < /etc/wireguard/ec2_private.key > /etc/wireguard/ec2_public.key'

Show the EC2 public key:

sudo cat /etc/wireguard/ec2_public.key

Example output:

lq/XAXLApDnawp3kkF9Iq7zsGEGI3lsIu703IbMWpE4=

Only the public key is needed on MikroTik.


  1. Add the EC2 Peer on MikroTik

On MikroTik, add the EC2 instance as a WireGuard peer.

Use the public key from:

sudo cat /etc/wireguard/ec2_public.key

MikroTik command:

/interface wireguard peers
add interface=wg-ec2 public-key="lq/XAXLApDnawp3kkF9Iq7zsGEGI3lsIu703IbMWpE4=" allowed-address=100.97.0.2/32

For this design, I do not need to set an endpoint on MikroTik.

The EC2 instance will initiate the connection to MikroTik, and MikroTik will learn the current endpoint after the first handshake.


  1. Configure WireGuard on EC2

Create the WireGuard configuration file on EC2:

sudo vi /etc/wireguard/wg0.conf

Example configuration:

[Interface]
Address = 100.97.0.2/32
PrivateKey = EC2_PRIVATE_KEY
MTU = 1420

[Peer]
PublicKey = H2MT9wlUE+UBn9qMEkilYChWAlHp9Q9YBYfX5yJcWy4=
Endpoint = home.maksonlee.com:51821
AllowedIPs = 100.97.0.1/32, 192.168.0.0/24
PersistentKeepalive = 25

Replace:

EC2_PRIVATE_KEY

with the content of:

sudo cat /etc/wireguard/ec2_private.key

The peer public key is the public key of the MikroTik wg-ec2 interface:

H2MT9wlUE+UBn9qMEkilYChWAlHp9Q9YBYfX5yJcWy4=

The endpoint is:

Endpoint = home.maksonlee.com:51821

In my test, this resolved to:

1.34.95.87:51821

The important part is:

AllowedIPs = 100.97.0.1/32, 192.168.0.0/24

This tells EC2:

Use wg0 to reach the MikroTik WireGuard IP.
Use wg0 to reach the homelab LAN.

Do not use this for this setup:

AllowedIPs = 0.0.0.0/0

That would send all IPv4 traffic from EC2 through the home router, which is not what I want here.

I also use:

PersistentKeepalive = 25

This helps keep the EC2-to-MikroTik WireGuard path active, which is useful because the EC2 instance is the side that initiates the tunnel.

Protect the WireGuard configuration file:

sudo chmod 600 /etc/wireguard/wg0.conf

  1. Start WireGuard on EC2

Enable and start WireGuard:

sudo systemctl enable --now wg-quick@wg0

Example output:

Created symlink '/etc/systemd/system/multi-user.target.wants/wg-quick@wg0.service' → '/usr/lib/systemd/system/wg-quick@.service'.

Check the WireGuard status.

When using Session Manager, I prefer to pipe the output through cat:

sudo wg show 2>&1 | cat

Example output:

interface: wg0
  public key: lq/XAXLApDnawp3kkF9Iq7zsGEGI3lsIu703IbMWpE4=
  private key: (hidden)
  listening port: 56485

peer: H2MT9wlUE+UBn9qMEkilYChWAlHp9Q9YBYfX5yJcWy4=
  endpoint: 1.34.95.87:51821
  allowed ips: 100.97.0.1/32, 192.168.0.0/24
  latest handshake: 30 seconds ago
  transfer: 28.94 KiB received, 40.63 KiB sent
  persistent keepalive: every 25 seconds

The key line is:

latest handshake: 30 seconds ago

That means the WireGuard tunnel is up.

The EC2 wg0 interface shows a local listening port:

listening port: 56485

This does not mean I need to open UDP 56485 on the EC2 security group. The EC2 instance initiated the WireGuard connection outbound to MikroTik. MikroTik learns the EC2 current endpoint dynamically.

Also check the interface:

ip addr show wg0

Example output:

3: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet 100.97.0.2/32 scope global wg0
       valid_lft forever preferred_lft forever

Check the route table:

ip route

Example output:

default via 10.20.16.1 dev ens5 proto dhcp src 10.20.30.151 metric 100
10.20.0.2 via 10.20.16.1 dev ens5 proto dhcp src 10.20.30.151 metric 100
10.20.16.0/20 dev ens5 proto kernel scope link src 10.20.30.151 metric 100
10.20.16.1 dev ens5 proto dhcp scope link src 10.20.30.151 metric 100
100.97.0.1 dev wg0 scope link
192.168.0.0/24 dev wg0 scope link

The important routes are:

100.97.0.1 dev wg0
192.168.0.0/24 dev wg0

This means EC2 will send traffic to the MikroTik WireGuard IP and the homelab LAN through the WireGuard tunnel.


  1. Verify from MikroTik

On MikroTik, check the peer:

/interface wireguard peers print detail

Example output:

Flags: X - DISABLED; D - DYNAMIC
 0    interface=wg-aws name="peer1" public-key="SW8uMhx6GROGZpAFkCb/jpkL59sKx0gKpiA3WUdaygo=" endpoint-address=3.109.96.219 endpoint-port=51820 current-endpoint-address=3.109.96.219 current-endpoint-port=51820 allowed-address=100.96.0.1/32,10.0.0.0/20,10.0.128.0/20
      persistent-keepalive=25s client-endpoint="" client-allowed-address="" rx=2744.6MiB tx=3570.7MiB last-handshake=31s

 1    interface=wg-ec2 name="peer2" public-key="lq/XAXLApDnawp3kkF9Iq7zsGEGI3lsIu703IbMWpE4=" endpoint-address="" endpoint-port=0 current-endpoint-address=43.212.14.245 current-endpoint-port=56485 allowed-address=100.97.0.2/32 client-endpoint=""
      client-allowed-address="" rx=40.7KiB tx=28.9KiB last-handshake=1m18s

The important part is:

interface=wg-ec2
current-endpoint-address=43.212.14.245
current-endpoint-port=56485
allowed-address=100.97.0.2/32
last-handshake=1m18s

This confirms that MikroTik learned the EC2 endpoint dynamically.


  1. Test from EC2 to MikroTik

From EC2, test the MikroTik WireGuard IP:

ping 100.97.0.1

Example output:

PING 100.97.0.1 (100.97.0.1) 56(84) bytes of data.
64 bytes from 100.97.0.1: icmp_seq=1 ttl=64 time=5.98 ms
64 bytes from 100.97.0.1: icmp_seq=2 ttl=64 time=5.78 ms
64 bytes from 100.97.0.1: icmp_seq=3 ttl=64 time=5.49 ms
64 bytes from 100.97.0.1: icmp_seq=4 ttl=64 time=5.78 ms
64 bytes from 100.97.0.1: icmp_seq=5 ttl=64 time=5.81 ms

--- 100.97.0.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4007ms
rtt min/avg/max/mdev = 5.486/5.768/5.984/0.160 ms

Then test the MikroTik LAN IP:

ping 192.168.0.1

Example output:

PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=5.75 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=5.75 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=5.86 ms
64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=5.65 ms
64 bytes from 192.168.0.1: icmp_seq=5 ttl=64 time=5.97 ms

--- 192.168.0.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4008ms
rtt min/avg/max/mdev = 5.648/5.796/5.970/0.110 ms

At this point, the EC2 instance can reach the MikroTik router through WireGuard.


  1. Access the EC2 Instance from Home

After the VPN is connected, I can access the EC2 instance from home using its WireGuard IP:

100.97.0.2

From a homelab machine:

ping 100.97.0.2

SSH example:

ssh ubuntu@100.97.0.2

This does not require an AWS route table change.

The traffic is going directly through the WireGuard tunnel.

The EC2 security group does not need a public inbound SSH rule for this VPN path because AWS only sees the outer WireGuard UDP traffic.


Why I Use the WireGuard IP Instead of the EC2 Private IP

The EC2 instance has an AWS private IP from the VPC.

In this example:

EC2 private IP: 10.20.30.151

But from home, I should not expect to connect to:

10.20.30.151

This setup is not routing the whole AWS VPC to my homelab.

Instead, I should connect to the EC2 WireGuard IP:

100.97.0.2

For example:

For example:

ssh ubuntu@100.97.0.2

If I want home machines to access the EC2 private IP directly, then I need a more complete routed design with AWS route table changes and an EC2 router or VPN gateway.

That is not the goal of this post.

Did this guide save you time?

Support this site

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top