Skip to content
Use an Instance as a NAT Gateway

Use an Instance as a NAT Gateway

This guide is provided for informational purposes. Because it involves third party tooling we cannot guarantee things still work exactly as described here.

In this recipe, an instance of your VPC acts as a NAT gateway for the other instances of its Subnet. Traffic they send to an external network is routed to the gateway, which replaces their source address with its own public IP before sending it on. External hosts only ever see the gateway’s public IP and can’t open connections back to the instances behind it.

NAT gateway setup

The setup uses three instances:

  • internal-vm is attached to the VPC Subnet and reaches the external network through the gateway.
  • gateway-vm is attached to the VPC Subnet and translates the addresses.
  • external-vm stands for the external network. It’s not attached to the VPC. It can also be a machine outside of Exoscale, as long as gateway-vm can reach it.

The VPC side is configured with the exo CLI. Only the forwarding and address translation on the gateway are configured inside the instance, with standard Linux tools.

Before You Start

You need:

  • The Exoscale CLI, configured with exo config.
  • An SSH key registered in your Exoscale account.

Set the following variables in your shell. The commands below refer to them:

export ZONE=ch-dk-2
export SSH_KEY=your-ssh-key-in-exoscale
export SG=allow-ssh-from-any
export VPC_CIDR=10.1.0.0/24

Step 1: Create the VPC and Subnet

exo networking vpc create my-vpc -z $ZONE
exo networking vpc subnet create my-vpc my-subnet -z $ZONE \
    --ipv4-block $VPC_CIDR --address-family inet4 --address-space private

Step 2: Create the Instances

Create a Security Group that allows SSH, then create internal-vm and external-vm:

exo compute security-group create $SG
exo compute security-group rule add $SG \
    --flow ingress --protocol tcp --port 22 --network 0.0.0.0/0

exo compute instance create internal-vm \
    --ssh-key $SSH_KEY --security-group $SG -z $ZONE
exo compute instance create external-vm \
    --ssh-key $SSH_KEY --security-group $SG -z $ZONE

Create gateway-vm with VPC IP forwarding enabled

exo compute instance create gateway-vm --ip-forwarding \
    --ssh-key $SSH_KEY --security-group $SG -z $ZONE

Step 3: Attach the Instances to the Subnet

Attach gateway-vm and internal-vm to the Subnet. external-vm stays outside of the VPC and only talks to the gateway over its public IP.

$ exo compute instance attach-to-subnet gateway-vm my-vpc my-subnet -z $ZONE
$ exo compute instance attach-to-subnet internal-vm my-vpc my-subnet -z $ZONE

Step 4: Allow the Traffic in Security Groups

The $SG Security Group only allows SSH. Add two more Security Groups:

  • internal-gateway-sg allows all traffic between gateway-vm and internal-vm inside the VPC.
  • gateway-external-sg allows all traffic between the public IPs of gateway-vm and external-vm.

Each one allows all traffic from the other members of the group:

exo compute security-group create internal-gateway-sg
exo compute security-group rule add internal-gateway-sg \
    --flow ingress --protocol all --security-group internal-gateway-sg
exo compute instance security-group add gateway-vm internal-gateway-sg -z $ZONE
exo compute instance security-group add internal-vm internal-gateway-sg -z $ZONE

exo compute security-group create gateway-external-sg
exo compute security-group rule add gateway-external-sg \
    --flow ingress --protocol all --security-group gateway-external-sg
exo compute instance security-group add gateway-vm gateway-external-sg -z $ZONE
exo compute instance security-group add external-vm gateway-external-sg -z $ZONE

In production, narrow these rules down to the protocols and ports your workload needs.

Step 5: Note the Addresses

Look up the public IP and the VPC address of each instance:

exo compute instance show internal-vm -z $ZONE
exo compute instance show gateway-vm -z $ZONE
exo compute instance show external-vm -z $ZONE

Save them as variables:

export GATEWAY_VPC_IP=<gateway-vm-vpc-ip>     # 10.1.0.20 in the diagram
export INTERNAL_VPC_IP=<internal-vm-vpc-ip>   # 10.1.0.10 in the diagram
export INT_PUB=<internal-vm-public-ip>
export GW_PUB=<gateway-vm-public-ip>
export EXT_PUB=<external-vm-public-ip>
export EXT_CIDR=$EXT_PUB/32   # the external range to reach through the gateway

Tip

If you exported all the variables mentioned above you can connect to the VMs with ssh ubuntu@$INT_PUB, ssh ubuntu@$GW_PUB and ssh ubuntu@$EXT_PUB respectively.

Step 6: Configure the VPC Network Interfaces

The new network interface (created in step 3) of each instance gets its address through DHCP. Some operating systems may not configure this automatically. In that case, connect to gateway-vm and internal-vm over SSH and start the DHCP client on the VPC interface yourself, for example on Ubuntu 22.04:

sudo dhclient eth1

Step 7: Configure the Gateway Instance

Connect to gateway-vm with ssh ubuntu@$GW_PUB. Enable forwarding in the operating system, so that the kernel routes packets between its interfaces, and keep the setting across reboots:

# on gateway-vm
sudo sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forwarding.conf

Find the public interface, usually eth0, then add a masquerading rule. Traffic from internal-vm leaving through this interface gets the gateway’s own address as its source:

# on gateway-vm
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

This rule is lost when the instance reboots. Persist it with the mechanism of your operating system, for example the iptables-persistent package on Ubuntu.

Step 8: Route the Traffic Through the Gateway

Add a Subnet route that sends the traffic for the external network to gateway-vm. $EXT_CIDR is the range you need to reach, here only external-vm:

exo networking vpc route create my-vpc -z $ZONE \
    --subnet my-subnet \
    --destination $EXT_CIDR \
    --target ip=$GATEWAY_VPC_IP \
    --description "internal-vm to external network via gateway-vm"

The platform doesn’t push this route to the instances of the Subnet. The operating system of internal-vm still sends this traffic through its public interface, so add a matching route through its VPC interface, with the same destination. The next hop is the Subnet gateway, the second address of the Subnet (10.1.0.1 here). The platform then applies the Subnet route and delivers the packets to gateway-vm:

# on internal-vm
sudo ip route add $EXT_CIDR via 10.1.0.1 dev eth1

This route is also lost when the instance reboots. Persist it in the network configuration of the instance, for example with netplan on Ubuntu.

Warning

Be careful when the destination is broad, for example 0.0.0.0/0 to reach the whole internet. A default route through the VPC interface replaces the one through the public interface of internal-vm, and connections to its public IP, including your SSH session, stop working.

Step 9: Test the Setup

From internal-vm, reach external-vm.

ubuntu@internal-vm:~$ ping $EXT_PUB

If the ping gets no reply, check that:

  • gateway-vm was created with --ip-forwarding and net.ipv4.ip_forward is 1.
  • The masquerading rule is present: sudo iptables -t nat -L POSTROUTING -v -n.
  • ip route get $EXT_PUB on internal-vm shows eth1 as the interface.
  • gateway-vm can reach external-vm directly with ping $EXT_PUB.
Last updated on