One Server is a Risk, Two is a Solution: Load Balancing Hands On (practice for better understanding)
Most developers have heard the term “Load Balancer.”
But very few actually build one themselves and see how it behaves in real time.
Last week, I decided to do exactly that.
I set up a working load balancing system on my local machine using Laravel 12, Docker, Nginx, MySQL, and Redis. No cloud, no expensive infrastructure — just a laptop and curiosity.
In this post, I’ll walk you through how it works in the simplest way possible.
What is a Load Balancer?
Let’s make this very simple.
Imagine a fast-food restaurant.
If there’s only one cashier and ten customers, everything slows down.
Now add a second cashier. Customers get split between them, and things move faster.
A load balancer is like the manager who decides which cashier will serve which customer.
In web applications, when thousands of users hit your app, a load balancer distributes those requests across multiple servers so no single server gets overwhelmed.
What I Built
Here’s the architecture:
Browser → Load Balancer (Nginx)
↓ ↓
Server 1 Server 2 ← Both run Laravel 12
↓ ↓
MySQL Redis ← Shared between both
Both Laravel servers use the same database and the same session storage.
So if a user logs in through Server 1, they will still be logged in even if the next request goes to Server 2.
The Two Types of Load Balancers
This part confused me a lot in the beginning, so let me explain it clearly.
L4 — Layer 4 (Transport Layer)
Think of this as a very fast but simple system.
It only looks at network-level information like IP and port.
It does not understand what’s inside the request.
Like a postman who only reads the address on the envelope and delivers it without opening it.
L7 — Layer 7 (Application Layer)
This one is smarter.
It actually understands HTTP requests.
It can read URLs, headers, cookies, and make decisions based on them.
Like a postman who opens the letter, reads it, and decides where it should go.
The Actual Code Difference
L7 Load Balancer (Nginx — HTTP Layer)
Layer 7 Nginx reads HTTP requests, inspects headers, and forwards requests intelligently:
upstream laravel_cluster {
server app_node_1:80;
server app_node_2:80;
}
server {
listen 80;
location / {
proxy_pass http://laravel_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
L4 Load Balancer (Nginx — TCP Layer, Round-Robin)
Layer 4 Nginx works purely at the TCP layer. It forwards connections, not requests, and cannot inspect headers:
stream {
upstream laravel_cluster_tcp {
server app_node_1:80;
server app_node_2:80;
# Round-robin is the default
}
server {
listen 80;
proxy_pass laravel_cluster_tcp;
}
}
L4 Example (MySQL and Redis)
mysql:
image: mysql:8.4
ports: ["3306:3306"]
redis:
image: redis:alpine
There are no headers here. No HTTP.
Just raw TCP connections.
That’s Layer 4.
This shows clearly:
Recommended by LinkedIn
How the Laravel Apps Are Exposed
This is something many tutorials skip, but it’s very important.
The Laravel apps are not exposed to your browser.
Only the load balancer is.
Think of it like this:
You (Browser)
↓
Load Balancer (only entry point)
↓ ↓
App 1 App 2
Now look at this:
nginx_lb:
ports: ["8000:80"]
app_node_1:
app_node_2:
Only the load balancer has a public port.
The app servers are completely hidden inside Docker’s internal network.
Docker automatically gives each container a name, and they communicate using those names internally.
So this works:
server app_node_1:80;
server app_node_2:80;
But you cannot open them in your browser directly.
What Runs Inside Each Laravel Container
Each Laravel container is not just Laravel.
It actually runs two processes:
Nginx → handles HTTP requests
PHP-FPM → executes PHP code
Started with:
CMD php-fpm -D && nginx -g 'daemon off;'
And internally:
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
}
Nginx forwards PHP requests to PHP-FPM.
Full Request Flow
Here’s what happens when you open the app:
1. Browser hits http://localhost:8000
2. Request goes to Load Balancer (Nginx)
→ Decides which server to use
3. Request goes to app_node_1 or app_node_2
→ Nginx inside container receives it
4. PHP requests go to PHP-FPM
→ Laravel executes
5. Laravel talks to:
→ MySQL (database)
→ Redis (sessions)
6. Response comes back:
→ App → Load Balancer → Browser
There are actually three Nginx instances:
This is completely normal.
The Shared Session Problem
Here’s a real issue:
User logs in on Server 1 Next request goes to Server 2 Server 2 doesn’t know the session
User gets logged out.
To fix this, both servers must share sessions.
SESSION_DRIVER=redis
REDIS_HOST=redis
Now both servers read and write sessions from Redis.
Problem solved.
How to Test It
Add this route to your web.php :
Route::get('/debug', fn() => "Server: " . env('NODE_NAME'));
Each container has a different name.
Now refresh:
http://localhost:8000/debug
You’ll see:
Server: Alpha
Server: Bravo
Server: Alpha
Server: Bravo
That means your load balancer is working.
follow README.md
If you like it give a star to the repository .Thanks.