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.

Example inputmain.tf
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
}
STACKIT Terraform by StackToITterraform/network.tf
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).

Example inputmain.tf
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
  }
}
StackToIT assessmentassessment.json
{
  "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 resourceSTACKIT targetMigration class
VPCSTACKIT project networking or Network AreaReconfigure
Subnets (public and app)STACKIT network with the same IPv4 prefixReconfigure
Internet gatewayImplicit: the STACKIT network default gatewayReconfigure
NAT gateway and Elastic IPImplicit outbound; the fixed egress address is not keptReconfigure
Route table with custom routesNo target drafted; routing design decided by an architectArchitect decision
Security groupSTACKIT security group and rulesReconfigure
VPN gateway, customer gateway, VPN connectionSTACKIT VPN gateway and connectionReconfigure
Route 53 private hosted zone and recordSelf-managed internal DNSRedesign

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.

Example inputmain.tf
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"]
  }
}
STACKIT Terraform by StackToITterraform/security.tf
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. 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. 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. 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. 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. 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. 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.