Unit 2.3: Load Balancing and Security as Code
Introduction
Your TaskBoard network stack from Unit 2.2 has three tiers wired together: a public LAN, an application LAN, and a database LAN. The API servers are reachable on the app LAN, but nothing fronts them yet, and nothing filters the traffic that reaches their NICs. In this unit you provision the edge.
You will put a Managed Application Load Balancer in front of the API, distributing HTTP requests across the app servers with path-based routing. You will see when a Network Load Balancer is the right tool instead, and you will lock down each tier with Network Security Group rules. The important IONOS-specific lesson here is where security actually binds: NSGs apply at the server-NIC level, never to the load balancer and never to Managed Kubernetes nodes. Get that wrong and you will spend hours wondering why your firewall rules have no effect on traffic that arrives through the ALB. Everything in this unit is code: Terraform resources and the raw Cloud API calls underneath them.
1. Application Load Balancer as Code
The ionoscloud_application_loadbalancer resource provisions a Layer 7 load balancer that terminates HTTP and HTTPS and routes by application-level attributes. The ALB sits between a listener LAN, where clients reach it, and a target LAN, where your backends live. For the TaskBoard API, the listener LAN is the public LAN and the target LAN is the app LAN.
An ALB is built from three resource types: the load balancer itself, one or more target groups that register backend targets, and forwarding rules that map a listener to those targets. A target group is a logical grouping of registered targets, where each target is any object with an IP address in your VDC, such as a VM or another load balancer. You can reuse the same target group across multiple forwarding rules.
1.1 Provisioning the load balancer and target group
Start with the load balancer and a target group. Each target is registered with an IP, a port, and a weight. The target weight range is 1 - 256, and you use it to send proportionally more traffic to larger backends.
resource "ionoscloud_application_loadbalancer" "taskboard_api" {
datacenter_id = ionoscloud_datacenter.taskboard.id
name = "taskboard-api-alb"
listener_lan = ionoscloud_lan.public.id
target_lan = ionoscloud_lan.app.id
ips = [ionoscloud_ipblock.alb_ip.ips[0]]
}
resource "ionoscloud_target_group" "api_targets" {
name = "taskboard-api-tg"
algorithm = "ROUND_ROBIN"
protocol = "HTTP"
targets {
ip = ionoscloud_server.api_1.nics[0].ips[0]
port = 8080
weight = 10
health_check_enabled = true
}
targets {
ip = ionoscloud_server.api_2.nics[0].ips[0]
port = 8080
weight = 10
health_check_enabled = true
}
}
A public ALB requires a reserved public IP, which is why the ips argument references an ionoscloud_ipblock. The supported load-balancing algorithms are Round Robin, Least Connections, Random, and Source IP, with Round Robin as the default.
1.2 Forwarding rules and listeners
Forwarding rules define how client traffic is distributed to the targets, and more than one rule can be created for the same load balancer. A forwarding rule binds a listener IP and port to an HTTP rule that forwards matched requests to a target group.
resource "ionoscloud_application_loadbalancer_forwardingrule" "api_http" {
datacenter_id = ionoscloud_datacenter.taskboard.id
application_loadbalancer_id = ionoscloud_application_loadbalancer.taskboard_api.id
name = "taskboard-api-fwd"
protocol = "HTTP"
listener_ip = ionoscloud_ipblock.alb_ip.ips[0]
listener_port = 80
http_rules {
name = "forward-api"
type = "FORWARD"
target_group = ionoscloud_target_group.api_targets.id
}
}
The ALB supports the HTTP and HTTPS protocols over HTTP1 and HTTP2. Beyond plain path-based forwarding, the routing types include hostname-based, query-string, header-based, method-based, cookie-based, source-IP-based, URL redirection, and static fixed responses. An ALB on your contract can hold from 1 to 10 listeners, and you can provision up to 5 ALBs per contract.
1.3 TLS, WebSocket, and gRPC
The ALB supports TLS offloading, so it terminates HTTPS and forwards plain HTTP to your backends, removing certificate handling from your application servers. SNI is supported, which lets a single listener serve multiple certificates by hostname. WebSocket and gRPC are both supported, so the same ALB fronts a REST API and a streaming endpoint without a separate proxy. The raw API call to create a WebSocket-capable ALB targets the applicationloadbalancers collection:
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '"$IONOS_TOKEN" \
https://api.ionos.com/cloudapi/v6/datacenters/$DC_ID/applicationloadbalancers \
-d '{"properties":{"name":"alb-websocket","listenerLan":1,"targetLan":2,"ips":["1.2.3.4"]}}'
2. Network Load Balancer as Code
When you need raw L4 throughput and do not need application-layer routing, the ionoscloud_networkloadbalancer resource provisions a Layer 4 load balancer. The NLB operates at OSI layer 4 and distributes TCP traffic across targets without inspecting the payload.
The NLB has a single listener interface that can support multiple IPs with different forwarding rules. A public NLB is exposed to and accepts client connections directly from the internet, serving as an edge device for north-south traffic. Its load-balancing algorithms are the same set as the ALB: Round Robin, Least Connections, Random, and Source IP.
2.1 Provisioning an NLB with forwarding rules
resource "ionoscloud_networkloadbalancer" "taskboard_nlb" {
datacenter_id = ionoscloud_datacenter.taskboard.id
name = "taskboard-nlb"
listener_lan = ionoscloud_lan.public.id
target_lan = ionoscloud_lan.app.id
ips = [ionoscloud_ipblock.nlb_ip.ips[0]]
}
resource "ionoscloud_networkloadbalancer_forwardingrule" "tcp" {
datacenter_id = ionoscloud_datacenter.taskboard.id
networkloadbalancer_id = ionoscloud_networkloadbalancer.taskboard_nlb.id
name = "taskboard-tcp-fwd"
algorithm = "SOURCE_IP"
protocol = "TCP"
listener_ip = ionoscloud_ipblock.nlb_ip.ips[0]
listener_port = 5432
targets {
ip = ionoscloud_server.db_1.nics[0].ips[0]
port = 5432
weight = 1
}
health_check {
client_timeout = 50000
connect_timeout = 5000
target_timeout = 50000
retries = 3
}
}
The NLB protocol set is TCP only; UDP is not supported, and HTTP health checks are not supported, so health checking is TCP-based. Session stickiness on the NLB uses Source IP affinity: a client session stays on the same target while its TCP sessions remain active. The default target weight is 1 with a maximum of 256, and the health-check default is 3 retries. You can provision up to 5 NLBs per contract.
2.2 Choosing ALB versus NLB
The decision is driven by what layer your routing needs to inspect. The following table summarizes the two options for code-time decisions:
| Capability | Managed ALB | Managed NLB |
|---|---|---|
| OSI layer | 7 | 4 |
| Protocols | HTTP, HTTPS | TCP |
| Routing | Path, host, header, method, cookie, query, source IP | TCP forwarding by listener port |
| TLS offloading | Yes | No |
| WebSocket / gRPC | Yes | No |
| Health check types | TCP, HTTP | TCP |
| Stickiness | Application-level routing rules | Source IP affinity |
Choose the ALB when you route by URL path, host, or header, or when you want the load balancer to terminate TLS. The TaskBoard API uses an ALB so that /api/tasks and /api/health can be routed and TLS terminated at the edge. Choose the NLB for non-HTTP TCP services, where you want minimal-overhead L4 distribution and the application handles its own protocol concerns.
3. Network Security Groups as Code
A Network Security Group is a stateful firewall that you attach to your VMs and NICs. The IONOS-specific rule that governs everything in this section: NSGs bind at the server-NIC level. They do not apply to the Managed ALB or NLB, and Managed Kubernetes node-pool nodes are excluded from NSGs. You secure load-balanced backends by writing rules on the backend NICs, not on the load balancer.
The default action of an NSG is deny-all, so traffic is blocked unless an explicit rule allows it. Rules carry a direction, either INGRESS or EGRESS, and both directions are supported. The supported protocols are TCP, UDP, ICMP, ICMPv6, GRE, VRRP, ESP, AH, and ANY.
3.1 Attaching NSGs and writing rules
Attachment granularity is per-VM, which covers all NICs of that VM, or per-NIC for granular control. When a VM is a member of a NSG, all NICs of the VM implicitly inherit the firewall rules. Each NIC can carry up to 10 NSGs, a VDC can hold up to 200 NSGs, and each NSG can hold up to 100 rules.
resource "ionoscloud_nsg" "app_tier" {
datacenter_id = ionoscloud_datacenter.taskboard.id
name = "taskboard-app-nsg"
description = "App tier: allow ALB to API port only"
}
resource "ionoscloud_nsg_firewallrule" "allow_api_from_app_lan" {
datacenter_id = ionoscloud_datacenter.taskboard.id
nsg_id = ionoscloud_nsg.app_tier.id
protocol = "TCP"
name = "allow-api-8080"
type = "INGRESS"
source_ip = "10.0.1.0/24"
port_range_start = 8080
port_range_end = 8080
}
Because the firewall is stateful, you write only the ingress rule for an inbound TCP service; the return traffic is allowed automatically without a matching egress rule.
3.2 The raw API and rule properties
Under the Terraform resources, an NSG is the security-group API entity and each rule is a firewall-rule entity. Only contract administrators, owners, and users with permissions to the VDC concerned can create and manage NSGs via the API. The POST request to add a rule targets the security group on a data center:
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '"$IONOS_TOKEN" \
https://api.ionos.com/cloudapi/v6/datacenters/$DC_ID/securitygroups/$NSG_ID/rules \
-d '{"properties":{"name":"allow-api-8080","protocol":"TCP","type":"INGRESS","sourceIp":"10.0.1.0/24","portRangeStart":8080,"portRangeEnd":8080}}'
The rule property names are name, protocol, sourceMac, ipVersion, sourceIp, targetIp, portRangeStart, portRangeEnd, icmpCode, icmpType, and type. A default NSG contains 4 predefined rules: allow all IPv4 egress, allow all IPv6 egress, allow IPv4 ingress only from 10.0.0.0/24, and allow IPv6 ingress only from the data center's allocated /56 CIDR. The net effect is that all outbound traffic is allowed while inbound is blocked except for the explicitly allowed ranges.
4. Securing the Load-Balanced Tier
The core architectural consequence of NSGs binding to NICs is that the ALB is not a security boundary you can configure with firewall rules. Traffic that the ALB forwards to your backend arrives at the backend NIC, and that NIC is where filtering happens. So you write your NSG rules to allow only the load balancer's traffic in.
For the TaskBoard app tier, the ALB lives on the listener (public) LAN and forwards into the app LAN. The app server NICs should accept TCP on the API port from the app LAN subnet only, and reject everything else. The DB tier NICs should accept PostgreSQL traffic only from the app LAN, never from the public LAN.
4.1 Tier isolation rules
# DB tier: PostgreSQL reachable only from the app subnet
resource "ionoscloud_nsg" "db_tier" {
datacenter_id = ionoscloud_datacenter.taskboard.id
name = "taskboard-db-nsg"
}
resource "ionoscloud_nsg_firewallrule" "allow_pg_from_app" {
datacenter_id = ionoscloud_datacenter.taskboard.id
nsg_id = ionoscloud_nsg.db_tier.id
protocol = "TCP"
name = "allow-pg-from-app"
type = "INGRESS"
source_ip = "10.0.1.0/24"
port_range_start = 5432
port_range_end = 5432
}
Because db_tier is a custom NSG with no rules of its own beyond the one you just wrote, it does not receive the platform's predefined default rules (those apply only to the VDC's own Default NSG); this stack gives you inbound PostgreSQL from the app subnet only, and you must add an explicit egress rule on db_tier if the database server needs any outbound connectivity (for example DNS, NTP, or patching). There is no rule that mentions the public LAN, so the database is unreachable from the internet by construction.
4.2 Attaching the NSG to backend NICs
The NSG only takes effect once it is attached to the NICs it should protect. Attach the app-tier NSG to the API server NICs and the db-tier NSG to the database server NICs. Since membership is inherited by all NICs of a member VM, you can attach at the VM level when a server has a single relevant NIC.
resource "ionoscloud_nsg_share" "api_nics" {
for_each = toset([ionoscloud_server.api_1.id, ionoscloud_server.api_2.id])
datacenter_id = ionoscloud_datacenter.taskboard.id
server_id = each.value
nsg_id = ionoscloud_nsg.app_tier.id
}
The result is a defence-in-depth posture written entirely in Terraform: the ALB distributes and terminates TLS, while the NSGs on the backend NICs enforce that only intended traffic reaches each tier.
API Reference Quick Card
Key API endpoints for load balancing and security:
| Method | Endpoint | Description |
|---|---|---|
POST |
/datacenters/{dcId}/applicationloadbalancers |
Create an ALB |
POST |
/datacenters/{dcId}/applicationloadbalancers/{id}/forwardingrules |
Add an ALB forwarding rule |
POST |
/datacenters/{dcId}/networkloadbalancers |
Create an NLB |
POST |
/datacenters/{dcId}/securitygroups |
Create a Network Security Group |
POST |
/datacenters/{dcId}/securitygroups/{id}/rules |
Add a firewall rule to an NSG |
Base URL: https://api.ionos.com/cloudapi/v6
Authentication: Authorization: Bearer <token>
Code Lab
Objective: Provision an ALB for the TaskBoard API with Terraform, add NSG rules to the app and DB tiers, and verify routing and isolation.
Prerequisites:
- IONOS Cloud account with API token (
IONOS_TOKENexported) - Terraform with the
ionoscloudprovider configured - The TaskBoard network stack from Unit 2.2 already applied (datacenter, LANs, servers)
Step 1: Reserve a public IP for the ALB
resource "ionoscloud_ipblock" "alb_ip" {
location = "de/fra"
size = 1
name = "taskboard-alb-ip"
}
Expected output:
ionoscloud_ipblock.alb_ip: Creation complete after 8s [id=...]
Step 2: Add the load balancer and target group
terraform apply -target=ionoscloud_application_loadbalancer.taskboard_api \
-target=ionoscloud_target_group.api_targets
Expected output:
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Step 3: Add the forwarding rule
terraform apply -target=ionoscloud_application_loadbalancer_forwardingrule.api_http
Expected output:
ionoscloud_application_loadbalancer_forwardingrule.api_http: Creation complete
Step 4: Create and attach the app-tier NSG
terraform apply \
-target=ionoscloud_nsg.app_tier \
-target=ionoscloud_nsg_firewallrule.allow_api_from_app_lan \
-target=ionoscloud_nsg_share.api_nics
Expected output:
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Step 5: Create the DB-tier NSG with the isolation rule
terraform apply -target=ionoscloud_nsg.db_tier \
-target=ionoscloud_nsg_firewallrule.allow_pg_from_app
Expected output:
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Step 6: Verify the ALB routes to the API
curl -i http://$(terraform output -raw alb_ip)/api/health
Expected output:
HTTP/1.1 200 OK
{"status":"ok"}
Step 7: Verify DB isolation from the public side
nc -zv -w 5 $(terraform output -raw db_public_check) 5432
Expected output:
nc: connect to ... port 5432 (tcp) timed out: Operation now in progress
Validation Checklist:
- [ ] ALB returns 200 on
/api/healththrough the reserved public IP - [ ] App NICs accept TCP 8080 from the app subnet only
- [ ] PostgreSQL on the DB tier is unreachable from outside the app subnet
Cleanup:
terraform destroy \
-target=ionoscloud_application_loadbalancer_forwardingrule.api_http \
-target=ionoscloud_application_loadbalancer.taskboard_api \
-target=ionoscloud_target_group.api_targets \
-target=ionoscloud_nsg.app_tier \
-target=ionoscloud_nsg.db_tier \
-target=ionoscloud_ipblock.alb_ip
Common Pitfalls
Developer mistakes to avoid with load balancing and security on IONOS Cloud:
-
Trying to attach an NSG to the load balancer
- Problem: You write firewall rules expecting them to filter traffic at the ALB, but they have no effect.
- Why it happens: NSGs bind at the server-NIC level only. They do not apply to the Managed ALB or NLB, and Managed Kubernetes node-pool nodes are excluded entirely.
- Fix: Write the rules on the backend NICs. Allow only the load-balancer-forwarded traffic in, and isolate each tier at its own NICs.
-
Writing egress rules for inbound services
- Problem: You add an ingress rule for TCP 8080 and a matching egress rule for the response, then traffic behaves inconsistently or you over-open the NSG.
- Why it happens: The IONOS NSG is a stateful firewall, so return traffic for an allowed inbound connection is permitted automatically.
- Fix: Write only the ingress rule for an inbound service. Reserve egress rules for connections your server initiates.
-
Forgetting the ALB needs a reserved public IP
- Problem:
terraform applyfails when creating a public ALB because no usable public IP is supplied. - Why it happens: A public ALB requires a reserved public IP, supplied via the
ipsargument. - Fix: Provision an
ionoscloud_ipblockfirst and referenceionoscloud_ipblock.alb_ip.ips[0]in both the load balancer and the forwarding rule listener.
- Problem:
Summary
You can now put a managed load balancer in front of a tier and secure that tier entirely in code. The ALB handles Layer 7 routing, TLS offloading, and protocols like WebSocket and gRPC, while the NLB gives you minimal-overhead Layer 4 TCP distribution. Network Security Groups enforce per-NIC, stateful, deny-all filtering, and you secure load-balanced backends by writing rules on the backend NICs because the load balancer itself is not an NSG target.
For TaskBoard, the API now sits behind an ALB on the public LAN, the app NICs accept only forwarded API traffic from the app subnet, and the database is reachable only from the app tier. The next unit provisions the storage tier those servers depend on.
Key Points:
- The ALB is built from three resources:
ionoscloud_application_loadbalancer,ionoscloud_target_group, andionoscloud_forwardingrule - Choose the ALB for HTTP and HTTPS application routing and TLS offloading; choose the NLB for L4 TCP distribution
- NSGs bind at the server-NIC level only, never to the ALB, NLB, or Managed Kubernetes nodes
- NSGs default to deny-all and are stateful, so write only ingress rules for inbound services
- Secure a load-balanced tier by filtering on the backend NICs and isolating each tier with its own NSG
Important Terminology:
- Target group: A logical grouping of registered targets (IP, port, weight) that an ALB distributes traffic to; reusable across forwarding rules.
- Forwarding rule: A rule binding a listener IP and port to targets, defining how the load balancer distributes client traffic.
- Listener: The client-facing interface of a load balancer that accepts connections on an exposed IP and configured port.
- Network Security Group (NSG): A stateful, deny-all firewall attached per-VM or per-NIC, holding up to 100 rules.
- TLS offloading: Terminating HTTPS at the ALB and forwarding plain HTTP to backends, removing certificate handling from application servers.
Next Steps
Continue Learning: Unit 2.4: Storage Provisioning as Code
Related Topics: