CLOUD NETWORK MIGRATION · GUIDE
Cloud network migration to STACKIT: routing, VPN, DNS and firewalls
Subnets, security groups and a VPN gateway convert almost one to one. Custom routes and private DNS do not: the engine holds them back for an architect instead of guessing. This guide follows one small network through a real StackToIT run and shows which resources become Terraform and which become decisions.
A site-to-site VPN, converted
A demo network written for this guide, assessed by StackToIT on 4 November 2026: one VPC, two subnets, a NAT gateway, a VPN to an office, a private DNS zone. Left: the VPN resources of the input. Right: the STACKIT Terraform the run wrote for them, with the review comments of the file left out.
resource "aws_vpn_gateway" "office" {
vpc_id = aws_vpc.core.id
}
resource "aws_customer_gateway" "office" {
bgp_asn = 65000
ip_address = "203.0.113.10"
type = "ipsec.1"
}
resource "aws_vpn_connection" "office" {
vpn_gateway_id = aws_vpn_gateway.office.id
customer_gateway_id = aws_customer_gateway.office.id
type = "ipsec.1"
static_routes_only = true
}resource "stackit_vpn_gateway" "office" {
project_id = var.project_id
display_name = "office"
plan_id = var.vpn_plan_id
routing_type = "ROUTE_BASED"
availability_zones = {
tunnel1 = var.availability_zones[0]
tunnel2 = var.availability_zones[1]
}
}
resource "stackit_vpn_connection" "office" {
project_id = var.project_id
display_name = "office"
gateway_id = stackit_vpn_gateway.office.gateway_id
tunnel1 = {
remote_address = var.office_peer_address
pre_shared_key_wo = var.office_pre_shared_keys[0]
pre_shared_key_wo_version = 1
phase1 = {
encryption_algorithms = ["aes256"]
integrity_algorithms = ["sha2_256"]
}
phase2 = {
encryption_algorithms = ["aes256"]
integrity_algorithms = ["sha2_256"]
}
}
tunnel2 = {
remote_address = var.office_peer_address
pre_shared_key_wo = var.office_pre_shared_keys[1]
pre_shared_key_wo_version = 1
phase1 = {
encryption_algorithms = ["aes256"]
integrity_algorithms = ["sha2_256"]
}
phase2 = {
encryption_algorithms = ["aes256"]
integrity_algorithms = ["sha2_256"]
}
}
static_routes = var.office_static_routes
}Three resources become two
The virtual private gateway and the VPN connection become a STACKIT VPN gateway and connection. The customer gateway carries only the address and ASN of the office device, so the run records it as a support resource of the connection and writes no block for it. The office address comes back as a variable.
Secrets stay out of the file
The two pre-shared keys are variables without a default, marked sensitive. The run asks for new keys because the on-premises device is configured again for the new peer; the keys of the old connection do not carry over.
Static routes become an input
Routes announced over the tunnel are an input list (office_static_routes), not a guess. The STACKIT VPN documentation lists policy-based routing, static routes and dynamic routing over BGP, so the choice between static and BGP is yours to make with the team that owns the office device.
Where the run stops and asks: routes and private DNS
From the same run. Left: the route table with a NAT route and a route to the office, and the private hosted zone. Right: how assessment.json records them (id, class, confidence and target; the reasons are summarised below).
resource "aws_route_table" "app" {
vpc_id = aws_vpc.core.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.core.id
}
route {
cidr_block = "192.168.0.0/16"
gateway_id = aws_vpn_gateway.office.id
}
}
resource "aws_route53_zone" "internal" {
name = "corp.example.internal"
vpc {
vpc_id = aws_vpc.core.id
}
}{
"id": "aws_route_table.app",
"result": {
"class": "MANUAL_REVIEW",
"confidence": "LOW"
}
}
{
"id": "aws_route53_zone.internal",
"result": {
"class": "REDESIGN",
"confidence": "MEDIUM",
"target": {
"service": "self-managed internal DNS (e.g. CoreDNS or BIND on servers or SKE)"
}
}
}The route table is held for an architect
Neither route can be written safely. STACKIT has no NAT gateway resource, so the default route to the NAT gateway is withheld, and the fixed egress address of that gateway does not carry over. The route to the office goes through the VPN configuration, which the STACKIT VPN FAQ names as the place for VPN routing. The class is MANUAL_REVIEW with LOW confidence, and each route is listed as not carried with what to do instead.
Private DNS is a redesign
Route 53 private zones answer only inside the associated VPCs or through an inbound endpoint of a hybrid setup. STACKIT DNS is public authoritative DNS: its records are publicly resolvable by design, and the zone ACL field enforces nothing. The run therefore classes the private zone as REDESIGN and proposes a self-managed resolver, for example CoreDNS or BIND on servers or on SKE, rather than publishing internal names.
Both findings appear before the build
Because the plan carries them as decisions, a team learns about the missing NAT address and the public DNS model while it can still choose, not after the first cutover.
How the network maps
| AWS resource | STACKIT target | Migration class |
|---|---|---|
| VPC | STACKIT project networking or Network Area | Reconfigure |
| Subnets (public and app) | STACKIT network with the same IPv4 prefix | Reconfigure |
| Internet gateway | Implicit: the STACKIT network default gateway | Reconfigure |
| NAT gateway and Elastic IP | Implicit outbound; the fixed egress address is not kept | Reconfigure |
| Route table with custom routes | No target drafted; routing design decided by an architect | Architect decision |
| Security group | STACKIT security group and rules | Reconfigure |
| VPN gateway, customer gateway, VPN connection | STACKIT VPN gateway and connection | Reconfigure |
| Route 53 private hosted zone and record | Self-managed internal DNS | Redesign |
Classes as the run decided them. The customer gateway is a support resource of the VPN, and the route table association and the DNS record are folded into the route table and the zone, which is why 14 resources appear as 11 units.
Firewall rules: stateful on both sides, with one trap
AWS security groups and STACKIT security groups are both stateful rule sets, so the rules convert one by one. Left: the input group. Right: what the run wrote.
resource "aws_security_group" "app" {
name = "app-tier"
vpc_id = aws_vpc.core.id
ingress {
description = "HTTPS from the office network"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["192.168.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}resource "stackit_security_group" "app" {
project_id = var.project_id
name = "app-tier"
}
resource "stackit_security_group_rule" "app_ingress_1" {
project_id = var.project_id
security_group_id = stackit_security_group.app.security_group_id
description = "HTTPS from the office network"
direction = "ingress"
ether_type = "IPv4"
ip_range = "192.168.0.0/16"
port_range = {
max = 443
min = 443
}
protocol = {
name = "tcp"
}
}Allow-all egress is not written
The source rule that allows all outbound traffic is covered by the STACKIT default egress rules, so the run writes no rule for it. If the workloads must not reach everything outside, restricting egress is a design task of its own: the draft keeps the platform default.
A group that nothing uses restricts nothing
In this demo no server uses the group, so the run marks it orphaned: its rules limit no traffic until a server or interface carries the group. Check the attachment of every group in the draft before you rely on it.
Address ranges change
A rule that names an AWS-internal range, such as a prefix list, has to be re-expressed with STACKIT network prefixes. Network ACLs have no stateless counterpart: in the published AWS Retail Store run the seven network ACLs are switched off by the module defaults, and the class the run records for them is REDESIGN, with STACKIT security groups as the target.
A sensible order for a network move
The order below is general practice, tied to what the run writes. The run itself sequences the units by dependency in migration/technical-sequencing.md.
1 · Inventory every address range
List the CIDRs of all VPCs, subnets and the office networks. AWS recommends non-overlapping ranges when several networks meet a common on-premises network; the same holds for STACKIT. Keep the prefixes of the subnets where machines keep their addresses.
2 · Build networks and security groups
These have no dependency on data and convert directly. Verify with a plan in a test project before anything else depends on them.
3 · Decide the routing design
Answer the held route table: shared egress address or a public IP per server that a third party allow-lists, and how office routes are carried by the VPN.
4 · Stand up the VPN in parallel
Create the STACKIT gateway and connection while the old one still runs. Agree the window with the owner of the office device, who must configure the new peer and keys.
5 · Provide DNS before servers move
Internal names must resolve from the new networks before the first workload arrives, so the self-managed resolver comes before the application waves.
6 · Cut over by name, keep the way back
Lower DNS TTLs ahead of time, switch names after the checks, and keep the old VPN and zone until the rollback window has passed.
Decisions the network architect owns
Egress address
Accept the shared, changeable egress address of the STACKIT network, or give servers a public IP where an external allow-list names the old NAT address.
Route design
Whether the target needs a routing table at all, and how routes to on-premises networks are carried: static routes or BGP on the VPN.
Internal DNS
Which resolver serves internal names, where it runs and how the STACKIT networks reach it. Public DNS zones can move to STACKIT DNS; private ones cannot.
Default name servers
The draft sets ipv4_nameservers on each network from one variable whose default is the three eu01 addresses of STACKIT's DNS Resolver. As of November 2026 STACKIT's resolver page says the service is not yet generally available and is intended only for workloads inside STACKIT, and the draft notes that it resolves public names only. Its variable description adds that a public project otherwise gets public resolvers and that a Network Area project uses the area's default name servers. Confirm the default or point it at your internal resolver.
VPN plan and cutover
The service plan of the gateway, the tunnel zones, who changes the office device and when.
Exposure of servers
STACKIT networks have no public and private split: a server is reachable when it has a public IP and its group allows it. Decide which servers get none.
Questions network teams ask
Does a STACKIT network need a NAT gateway?
No NAT gateway resource exists. In a project outside a Network Area the default network routing gives outbound access, and the run records the NAT gateway as implicit outbound. What you lose is the fixed egress address of the AWS gateway, which the run reports as a decision.
Can STACKIT DNS replace a Route 53 private hosted zone?
Not directly. STACKIT DNS is public authoritative DNS, its records are publicly resolvable and the zone ACL is not enforced. Public zones move to it; internal names need a resolver of your own.
Is the VPN a one-to-one copy of the AWS connection?
It follows the same shape: IPsec with IKEv2 and two tunnels. The peer addresses, pre-shared keys and tunnel parameters are new, and the device on your side is configured again. The STACKIT VPN lists bandwidth from 100 to 1,000 Mbit/s.
Why does the run not write my routes?
A routing table that holds none of the source routes would send traffic where the source table did not. The engine withholds it, names every route as not carried and leaves the design to you.
Sources
Your next move: STACKIT.
Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.