DEV Community

Ipadeola Taiwo
Ipadeola Taiwo

Posted on

Building a Multi-Region Load Balancer on Azure for a Nigerian Fintech (Lagos ↔ Abuja) — A Real Troubleshooting Story

The Scenario
Imagine a Nigerian fintech — think Flutterwave-style — with offices in Lagos **and **Abuja. Both offices need to:

  1. Sit on the same private network (even though they're in different Azure
    regions)Serve a web app from two VMs, load-balanced, so if one office's
    server goes down, traffic keeps flowing through the other

  2. This post walks through exactly how I built this on Azure — including every error I hit along the way, because honestly, the errors taught me more than the parts that worked first try.

If you're following along, you'll need an Azure account (the free tier works) and basic comfort with SSH.

Part 1: Two VNets, Two Regions, One Private Network
The first task was creating two Virtual Networks (VNets) in different Azure regions and peering them together so they could talk to each other privately — no public internet involved.

  • VNet 1 (Lagos) → South Africa North
  • VNet 2 (Abuja) → East US 2

After creating both VNets, I set up VNet Peering between them. This is what allows resources in one VNet to reach resources in the other using private IP addresses, as if they were on the same local network — even though they're physically thousands of kilometers apart.

Part 2: One VM Per Region, Proving They Can Talk
Next, I deployed one Linux VM in each VNet:

vm-lagos in South Africa North
vm-abuja in East US 2

To prove the peering actually worked, I SSH'd into each VM and pinged the other one's private IP address:

ping 10.1.0.4
Enter fullscreen mode Exit fullscreen mode

Getting replies back here was the moment it clicked — two VMs in completely different parts of the world, talking over a private network, no public internet hop required.

Part 3: The Load Balancer — Where Things Got Interesting
This is the part of the project that taught me the most, because almost nothing went smoothly. I'll walk through it the way it actually happened, mistakes included, because I think that's more useful than a sanitized "perfect setup" guide.

My First Plan (That Didn't Work)
My first instinct was: "just create one regular Azure Load Balancer, point it at both VMs, done."
That seemed reasonable until I remembered a key constraint: a standard Azure Load Balancer only works within a single region. My two VMs were in two different regions. A regional load balancer physically can't span across them.
So I had two real options:
Option 1
Fake it by routing cross-region traffic through one region's LB
What it means
Works in theory, but not how Azure actually supports it in production

Option 2
Use Azure's Cross-Region (Global) Load Balancer
What it means
The actual purpose-built solution for exactly this scenario
I went with the proper solution: the Global Load Balancer.

You can't just create one Global LB and point it straight at two VMs. The Global LB sits on top of two regional load balancers — one per region — and routes traffic between regions, while each regional LB handles traffic within its own region.
This means I needed three load balancers total:

  1. A Regional LB in South Africa North (for Lagos)
  2. A Regional LB in East US (for Abuja)
  3. One Global LB sitting above both

Step 1: Web Server on Both VMs
Before touching any load balancer, both VMs needed something to actually serve over HTTP. I installed apache2 on each and customized the homepage so I could visually tell which VM was responding:

sudo apt update && sudo apt install apache2 -y
sudo systemctl start apache2
sudo systemctl enable apache2
echo "<h1>Lagos Server Loaded</h1>" | sudo tee /var/www/html/index.html
Enter fullscreen mode Exit fullscreen mode

(and the equivalent "Abuja Server Loaded" on the other VM)

Step 2: Open Port 80
Each VM's Network Security Group (NSG) needed an inbound rule allowing TCP traffic on port 80, otherwise the web server would be running but unreachable from outside.

Step 3: Build the Two Regional Load Balancers
I created one Standard SKU Public Load Balancer in each region, each with:

  • A frontend public IP
  • A backend pool containing that region's VM
  • A load balancing rule on port 80
  • A health probe checking HTTP on port 80

At this point, hitting either regional LB's public IP directly in a browser correctly returned that region's "Hello from..." page. Good sign — the foundation was solid.

Part 4: Where I Actually Got Stuck (And How I Fixed Each Thing)
This is the part I really want to highlight, because I think showing the actual errors is more useful than pretending everything worked first try.
🔴 Error 1: "No matching inventory for the requested VIP"
When I tried to create the Global Load Balancer for the first time, Azure threw this:
"No matching inventory for the requested VIP of address family: IPv4, sku: Standard and availability zone: non-zonal."

What was actually happening: Azure's Cross-Region/Global Load Balancer can only be deployed in specific "Home regions" — a fixed list that includes places like East US 2, West Europe, Southeast Asia, and a handful of others. I had originally tried creating it in a region that wasn't on that list.
The fix: I deleted the failed Public IP and Load Balancer, and recreated both in East US 2 specifically, with the Public IP's tier set to Global.

🤔 Question I had to clear up: "Does everything need to move to a Home region?"
Once I learned about the Home region restriction, I panicked a little — did my Resource Group, VNets, and VMs all need to be in one of these special regions too? That would've defeated the entire point of the assignment (having Lagos and Abuja in genuinely different regions).
Turns out: no. Only the Global Load Balancer itself and its Public IP need to be in a Home region. Everything else — the Resource Group, both VNets, both VMs, and both Regional Load Balancers — can stay exactly where they logically belong. The Home region is just where the Global LB's control plane lives; it doesn't dictate where your actual infrastructure sits.

🔴 Error 2: Global LB deployed, but curl couldn't connect at all
After fixing the region issue, the Global LB deployed successfully. But testing it with:

curl http:/20.15.2.121
Enter fullscreen mode Exit fullscreen mode

...just hung and timed out.

What was actually happening: When I went to check the regional backend pool configuration, I found the Virtual Network field was empty — meaning the backend pool existed and was attached to a load balancing rule, but had zero actual VMs inside it. It looked configured, but it was an empty shell.

The fix: Selecting the correct VNet from the dropdown revealed an "+ Add" button I hadn't seen before, which let me actually add the VM's NIC to the pool.

Lesson learned: a backend pool being "attached to a rule" doesn't mean it has members in it. Always double check the actual VM/IP table, not just whether the pool exists.

Final puzzle: "Why does my Global LB always show the same region?"
Everything was technically working now — both regional LBs responded correctly, and the Global LB no longer timed out. But running curl against the Global LB's IP over and over only ever returned the Abuja server, never Lagos, no matter how many times I refreshed or switched browsers.

My first thought: something's broken, it should be alternating like a normal load balancer.

What was actually happening: This is expected behavior, not a bug. A regional load balancer round-robins between backend VMs. A Global Load Balancer works completely differently — it uses geo-proximity routing, meaning it looks at where the request is physically coming from and always sends it to the closest healthy region. Since I was testing from one physical location every time, it consistently picked the same region — exactly as designed.

This actually makes total sense for the original use case: a Lagos-based customer should get routed to the Lagos backend, and a US-based customer should get routed to the US backend, automatically, without anyone configuring anything manually.

How I proved both regions actually worked:

  1. Curl each regional LB's IP directly — both returned their correct, distinct pages.
  2. Simulated a regional failure by stopping apache2 on the Abuja VM, then curled the Global LB IP again — it correctly failed over and served the Lagos page instead.


# On the Abuja VM
sudo systemctl stop apache2
Enter fullscreen mode Exit fullscreen mode
# From my laptop
curl http:/20.15.2.121
# Now returns "Lagos Server Loaded" instead
Enter fullscreen mode Exit fullscreen mode

Don't forget to restart apache2 afterward:

sudo systemctl start apache2
Enter fullscreen mode Exit fullscreen mode

What I'd Tell Someone Starting This Project
If I were doing this again, here's what I'd want to know upfront:

  • A regional Load Balancer cannot span two regions, full stop. If your VMs are in different regions, you need the Global tier sitting on top of two regional LBs underneath it — not one LB trying to do everything.
  • The Global LB has region restrictions ("Home regions") that your other resources don't. Don't panic and try to move your whole architecture — only the Global LB and its Public IP care about this.
  • Always verify backend pools actually have members, not just that they exist. An empty backend pool with a rule attached looks deceptively "configured."
  • Global Load Balancer ≠ round robin. It's geo-proximity based. If you're testing from one location and only ever see one region respond, that's correct behavior, not a failure — test failover instead to prove redundancy.

Final Result
By the end, hitting the Global Load Balancer's public IP correctly served traffic from the geographically closest healthy region, and killing nginx on one VM correctly triggered failover to the other — proving genuine multi-region high availability, the same pattern a real fintech would rely on to keep Lagos and Abuja customers online even if one region had issues.
If you're working through something similar, I hope walking through the actual errors (not just the final clean steps) saves you some of the head-scratching it cost me.
Questions or got stuck somewhere different? Drop a comment below 👇

Top comments (0)