---
title: "Use an Instance as a NAT Gateway"
description: "Set up a Linux NAT gateway in an Exoscale VPC: Subnet route, IP forwarding, iptables masquerading and Security Groups for outbound-only access."
url: https://community.exoscale.com/product/networking/vpc/recipes/nat-gateway/
product: "VPC"
section: product
last_updated: 2026-10-26
---
> For AI agents: the documentation index is at https://community.exoscale.com/llms.txt. Every page is available as markdown at `<page URL>index.md` or with `Accept: text/markdown`.

# 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.drawio.png "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](https://github.com/exoscale/cli/releases/latest), 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:

```console
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

```console
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`:

```console
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

```console
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.

```console
$ 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:

```console
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:

```console
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:

```console
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:

```console
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:

```console
# 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:

```console
# 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`:

```console
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`:

```console
# 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`.

```console
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`.

