Using Routing Rules
Routes decide where the platform delivers a packet that leaves an instance through a VPC interface. Every VPC comes with routes that make its Subnets reachable from each other. Most of the time you don’t need to touch the VPC routing rules, but for some advanced use cases, you can add your own routes to send traffic through an instance of your choice, such as a NAT gateway, a firewall, or a VPN endpoint.
How VPC Routing Works
VPC Routes and Subnet Routes
A VPC has two kinds of routes:
| VPC route (implicit) | Subnet route (custom) | |
|---|---|---|
| Created by | Exoscale, when a Subnet is added | You |
| Scope | Every Subnet of the VPC | The Subnet it is created in |
| Destination | The IPv4 block of the Subnet it was created for | Any IPv4 CIDR, including 0.0.0.0/0 |
| Target | cidr=<Subnet block> | ip=<IPv4 address> |
| Can be deleted | No, it is removed along with its Subnet | Yes |
A VPC route with the target cidr=<block> means deliver the packet directly to its destination
address. You will only see this target in the output of exo networking vpc route list. Routes you create always use
ip=.
$ exo net vpc show my-first-vpc
┼─────────────┼──────────────────────────────────────┼
│ VPC │ │
┼─────────────┼──────────────────────────────────────┼
│ ID │ 53df4f25-365e-4d48-be28-45f5de1c0bd1 │
│ Name │ my-first-vpc │
│ Description │ │
│ Default │ false │
│ Zone │ ch-gva-2 │
│ Created At │ 2026-08-12 07:32:45 +0000 UTC │
│ Labels │ n/a │
│ Subnets │ │
│ │ my-first-subnet 10.42.0.0/16 │
│ │ my-second-subnet 10.26.0.0/16 │
│ │ │
┼─────────────┼──────────────────────────────────────┼
$ exo net vpc route list my-first-vpc
┼──────────────────────────────────────┼──────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
│ ID │ KIND │ DESTINATION │ TARGET │ DESCRIPTION │
┼──────────────────────────────────────┼──────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
│ 052c8d9e-be35-4fea-9fc0-03925494612c │ Vpc │ 10.26.0.0/16 │ cidr=10.26.0.0/16 │ Default route for subnet 366f2dab-f52b-46bb-a8e3-49220ef8b5ff │
│ b8b6b692-07c5-4118-9134-ed80bccaa604 │ Vpc │ 10.42.0.0/16 │ cidr=10.42.0.0/16 │ Default route for subnet 1ea45a36-9085-4766-95a1-4397c208480a │
┼──────────────────────────────────────┼──────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
A Subnet route with the target ip=<IPv4 address> means deliver the packet to this next hop. The
packet is not modified. It reaches the next hop with its original destination address, and the next
hop decides what to do with it.
Which Routes Apply to an Instance
Routes are evaluated when a packet leaves an instance through a VPC interface. For that interface, the platform merges:
- all the VPC routes of the VPC, and
- the Subnet routes of the Subnet this interface is attached to.
If an instance is attached to several Subnets, each of its interfaces uses the routes of its own Subnet and it’s the instance’s operating system that chooses the interface the packet leaves from.
Traffic in the other direction is routed with the routes of the replying instance’s Subnet. Keep this in mind when you insert an appliance in the path (see Keep routing symmetric).
How a Route Is Selected
When several routes match the destination of a packet:
- The most specific route (the longest prefix) wins.
- If two routes have the same destination, the Subnet route wins over the VPC route.
- If no route matches, the packet is dropped.
Because of rule 2, you can override the implicit route to a Subnet by adding a Subnet route with the same destination.
For example, suppose the instances of the Subnet app (10.0.1.0/24) see these routes:
| Kind | Destination | Target |
|---|---|---|
| Vpc | 10.0.0.0/24 | cidr=10.0.0.0/24 |
| Vpc | 10.0.1.0/24 | cidr=10.0.1.0/24 |
| Vpc | 10.0.2.0/24 | cidr=10.0.2.0/24 |
| Subnet | 0.0.0.0/0 | ip=10.0.0.10 |
| Subnet | 10.0.2.0/24 | ip=10.0.3.10 |
| Packet destination | Selected route | Delivered to |
|---|---|---|
10.0.1.25 | Vpc 10.0.1.0/24 | 10.0.1.25 |
10.0.0.40 | Vpc 10.0.0.0/24 | 10.0.0.40 |
10.0.2.7 | Subnet 10.0.2.0/24 (wins tie) | 10.0.3.10 |
1.1.1.1 | Subnet 0.0.0.0/0 | 10.0.0.10 |
Preparing an Instance to Act as a Next Hop Router
The target of a Subnet route must be the VPC address of an instance attached to the same VPC. That instance receives packets addressed to other machines and sends them back out with a source address that is not its own. Four things must be in place for this to work.
Ensure IP forwarding is enabled on the instance. By default, the Exoscale platform drops any packet an instance sends through a VPC interface with a source address that is not the instance’s own. An instance used as a next hop must be created with VPC IP forwarding enabled. This setting can only be chosen when the instance is created.
exo compute instance create gateway-vm --ip-forwarding
Forwarding enabled in the operating system. On Linux:
ubuntu@appliance:~$ sudo sysctl -w net.ipv4.ip_forward=1
To keep this setting across reboots, add net.ipv4.ip_forward=1 to a file in /etc/sysctl.d/.
Security Group rules that allow the forwarded traffic. Security Groups filter the next hop’s VPC interface like any other interface. Add ingress rules on the next hop for the traffic it should forward, matching the source Subnets.
Operating system routes that use the VPC interface. This is the limitation described in Cross Subnet communication. The operating system must send VPC-bound traffic through a VPC interface, not through the public interface. This applies to the next hop and to the instances sending traffic to it.
Tip
Attach your appliances to a dedicated Subnet. Subnet routes only apply to the Subnet they are created in, so the traffic you steer to an appliance is never sent back to the appliance itself.
Managing Subnet Routes with the CLI
Create a Subnet route:
$ exo networking vpc route create my-first-vpc \
--subnet my-first-subnet \
--destination 0.0.0.0/0 \
--target ip=10.26.0.10 \
--description "Some route to a home-made NAT gateway"
Now we can see this route in the list of routes of the subnet
$ exo networking vpc route list my-first-vpc --subnet my-first-subnet
┼──────────────────────────────────────┼────────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
│ ID │ KIND │ DESTINATION │ TARGET │ DESCRIPTION │
┼──────────────────────────────────────┼────────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
│ 052c8d9e-be35-4fea-9fc0-03925494612c │ Vpc │ 10.26.0.0/16 │ cidr=10.26.0.0/16 │ Default route for subnet 366f2dab-f52b-46bb-a8e3-49220ef8b5ff │
│ b8b6b692-07c5-4118-9134-ed80bccaa604 │ Vpc │ 10.42.0.0/16 │ cidr=10.42.0.0/16 │ Default route for subnet 1ea45a36-9085-4766-95a1-4397c208480a │
│ ad2a856f-0d23-475d-8635-41b5d7a89270 │ Subnet │ 0.0.0.0/0 │ ip=10.26.0.10 │ Some route to a home-made NAT gateway │
┼──────────────────────────────────────┼────────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
And if we list the routes of the other subnet, we can see that it isn’t impacted by the route
$ exo networking vpc route list my-first-vpc --subnet my-second-subnet
┼──────────────────────────────────────┼──────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
│ ID │ KIND │ DESTINATION │ TARGET │ DESCRIPTION │
┼──────────────────────────────────────┼──────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
│ 052c8d9e-be35-4fea-9fc0-03925494612c │ Vpc │ 10.26.0.0/16 │ cidr=10.26.0.0/16 │ Default route for subnet 366f2dab-f52b-46bb-a8e3-49220ef8b5ff │
│ b8b6b692-07c5-4118-9134-ed80bccaa604 │ Vpc │ 10.42.0.0/16 │ cidr=10.42.0.0/16 │ Default route for subnet 1ea45a36-9085-4766-95a1-4397c208480a │
┼──────────────────────────────────────┼──────┼──────────────┼───────────────────┼───────────────────────────────────────────────────────────────┼
Route changes apply to running instances without a reboot and are usually propagated within a minute.
Keep Routing Symmetric
Each direction of a connection is routed by the Subnet of the instance that sends it. Suppose a Subnet route on Subnet A sends the traffic for Subnet B through an appliance. If Subnet B has no matching route, the replies from B follow the VPC route and go straight back to A, bypassing the appliance. A stateful appliance, such as a firewall, that sees only half of a connection usually drops it. When you insert an appliance between two Subnets, add a route pointing to it on both sides.
Example: Isolating Subnets from Each Other
By default, every Subnet of a VPC can reach every other Subnet. Security Groups are the main way to restrict this traffic. If you want to be sure that two Subnets can never communicate, you can add Subnet routes that point their traffic to an address where no instance will ever exist. Packets sent to such a target are dropped by the platform, whatever the Security Group rules allow.
Use an address outside the RFC 1918 ranges as the target, for example 192.0.2.1 from the
documentation range defined in RFC 5737. Subnet
blocks are always in the RFC 1918 ranges, so no instance can ever be attached with this address.
For example, take a VPC my-vpc with three Subnets:
subnet-a(10.1.0.0/16)subnet-b(10.2.0.0/16)subnet-c(10.3.0.0/16)
To let subnet-b talk to both of the others while keeping subnet-a and subnet-c apart, add a
route on each side of the forbidden pair:
$ exo networking vpc route create my-vpc \
--subnet subnet-a \
--destination 10.3.0.0/16 \
--target ip=192.0.2.1 \
--description "Isolate from subnet-c"
$ exo networking vpc route create my-vpc \
--subnet subnet-c \
--destination 10.1.0.0/16 \
--target ip=192.0.2.1 \
--description "Isolate from subnet-a"
Each route has the same destination as the VPC route of the other Subnet, so it takes precedence (see How a route is selected). The other VPC routes are untouched:
| Between | Result |
|---|---|
subnet-a ↔ subnet-b | Allowed |
subnet-b ↔ subnet-c | Allowed |
subnet-a ↔ subnet-c | Dropped |
| Inside each Subnet | Allowed |
Note
A route only applies to the traffic leaving the Subnet it is created in. With a route on one side only, connections still fail because the replies are dropped, but one-way traffic, such as UDP, still gets through from the other side. Add a route on both Subnets of the forbidden pair.
Warning
An instance attached to both subnet-a and subnet-c can still reach both Subnets directly
through its own interfaces. Do not attach instances to both Subnets of a pair you want to isolate.
To allow subnet-a and subnet-c to communicate again, delete these two routes.
Other Examples
The VPC recipes show more complete setups:
Troubleshooting
When traffic doesn’t get through, check that:
- A route matches the destination. Packets with no matching route are dropped, for example internet
traffic from a Subnet without a
0.0.0.0/0route, - The target IP belongs to an instance attached to the VPC. Packets sent to any other target are dropped,
- The next hop was created with VPC IP forwarding enabled, and forwarding is enabled in its operating system,
- The Security Groups of the next hop and of the destination allow the traffic,
- The operating system of each instance sends the traffic through its VPC interface (
ip route get <destination>shows the interface used).