How to set up a load balancer for your MU Online server
Distribute the load of your MU Online server's ConnectServer and website across multiple instances with a load balancer, reducing lag during peak hours and increasing infrastructure resilience.
As a MU Online server grows, the first scaling pain usually shows up at login: connection queues, ConnectServer timeouts, or the site going down right during peak hours, when the most players try to log in at once. A load balancer distributes this load across multiple instances of the same service,
As a MU Online server grows, the first scaling pain usually shows up at login: connection queues, ConnectServer timeouts, or the site going down right during peak hours, when the most players try to log in at once. A load balancer distributes this load across multiple instances of the same service, preventing a single process or machine from becoming a bottleneck. This tutorial shows how to apply load balancing at the ConnectServer, site, and auxiliary API layer of a MU server, including a practical Nginx setup, health checks, and load testing.
What can (and can't) be balanced on a MU server
The typical MU Online server architecture has components with very different scaling characteristics:
| Component | Keeps per-player state? | Ease of balancing |
|---|---|---|
| GameServer (game world) | Yes, full session and map state | Hard — usually scales via sharding (multiple servers/worlds), not traditional load balancing |
| ConnectServer | No, only routes to the correct GameServer | Easy — multiple instances behind a balancer |
| Institutional site/store | No (or state in a database/external session) | Easy — standard pattern for any web application |
| Admin panel API | Depends on implementation | Generally easy, if stateless |
| Database | Yes, it's the source of truth | Scales via replication/read replicas, not simple load balancing |
Understanding this table avoids the mistake of trying to "put a load balancer in front of the GameServer" expecting it to fix map lag — that doesn't work the same way, because game state lives inside a specific process.
Scenarios that justify a load balancer
Before investing time in configuration, confirm the symptom matches the right problem:
- Connection queue or timeout on the ConnectServer during peak hours, even with normal CPU/RAM.
- Site or store down exactly when the server is most crowded (event, season reset).
- Slow admin panel for staff during the same peaks.
If the symptom is lag inside the game maps, the problem is probably somewhere else (script optimization, monster count, the GameServer's own database) — it's worth investigating that separately before assuming a lack of load balancing is the cause.
Reference architecture with Nginx
A simple, effective architecture to start with uses Nginx as a load balancer in front of multiple site/API instances and, where applicable, the ConnectServer:
┌──────────────┐
Players ──► │ Nginx (LB) │
└──────┬───────┘
┌────────────┼────────────┐
▼ ▼ ▼
Instance 1 Instance 2 Instance 3
(site/API) (site/API) (site/API)
Configuring site load balancing with Nginx
A basic upstream block in nginx.conf distributes HTTP requests across multiple instances of the site application:
upstream mu_site_backend {
least_conn;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
server_name yoursite.com;
location / {
proxy_pass http://mu_site_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
The least_conn directive routes each new request to the instance with the fewest active connections at that moment, preventing one instance from getting overloaded while another sits idle — generally more effective than plain round-robin for irregular traffic like that of a MU server.
Health checks: automatically removing failed instances
A load balancer without health checking keeps sending traffic to a frozen instance, making the experience worse instead of better. Set up a simple health check endpoint in the application (e.g., /health, returning 200 if healthy) and, in Nginx (or using the nginx_upstream_check_module module / solutions like HAProxy for more robust active checking), monitor that endpoint periodically:
upstream mu_site_backend {
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
}
With max_fails and fail_timeout, Nginx stops sending traffic to an instance after 3 consecutive failures, giving it 30 seconds before trying again — reducing the impact of a frozen instance on players.
Balancing the ConnectServer
The ConnectServer is responsible for authenticating the initial connection and directing the client to the correct GameServer — it doesn't hold the game session state itself, which makes it easier to replicate across multiple instances. The most common approach is running 2 or more ConnectServer instances on different ports (or machines) and using a layer-4 (TCP) balancer, since the ConnectServer protocol isn't HTTP:
frontend mu_connect
bind *:44405
mode tcp
default_backend mu_connect_pool
backend mu_connect_pool
mode tcp
balance leastconn
server connect1 127.0.0.1:44406 check
server connect2 127.0.0.1:44407 check
This example uses HAProxy-style syntax, more suitable than plain Nginx for low-level TCP balancing like the ConnectServer's — Nginx also supports stream for this, but HAProxy tends to be the more mature choice in this specific scenario.
Session and affinity (sticky sessions)
For the site and admin panel, if the application stores login sessions in local memory (not in a shared database/Redis), "sticky sessions" need to be configured — ensuring the same player/admin always lands on the same instance during their session, avoiding unexpected logouts when redirected to another instance. The more robust solution, however, is migrating session storage to a shared store (Redis, database), making any instance interchangeable and balancing simpler and more resilient.
Testing balancing under load
Before trusting the configuration in production, simulate concurrent traffic with a load-testing tool (ab, wrk, or k6):
wrk -t4 -c200 -d30s http://yoursite.com/
This command simulates 200 concurrent connections for 30 seconds using 4 threads. During the test, watch whether the load is distributed evenly across instances (via Nginx/HAProxy logs or metrics) and whether response time stays stable even under this volume.
Monitoring the load balancer in production
Once active, continuously monitor:
| Metric | Where to observe | Why it matters |
|---|---|---|
| Requests per instance | Nginx/HAProxy logs or exposed metrics | Detects load imbalance |
| Response time per instance | APM or logs with request timing | Identifies a degraded instance |
| Health check failures | Load balancer logs | Alerts before the player notices |
| CPU/RAM usage per instance | System monitoring (htop, Grafana) | Confirms the balancing is actually distributing load |
Scaling gradually as your base grows
You don't need to start with an elaborate multi-physical-machine architecture. A common evolution path:
- A single site/ConnectServer instance, no load balancer (initial phase).
- Multiple instances on the same VPS, balanced locally by Nginx (growth phase).
- Multiple instances on different VPSs, with a dedicated balancer and Redis-shared session (scale phase).
- Geographic balancing (multiple data centers/regions), reserved for large servers with an international base.
Moving to the next phase before it's needed just adds operational complexity without real benefit — scale according to actual overload symptoms as they appear.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Player logged out when reloading the site | Session in local memory with no sticky session or shared storage | Migrate session to Redis/database or configure sticky sessions |
| Balancer keeps sending traffic to a frozen instance | No health check configured | Configure a health endpoint and max_fails/fail_timeout |
| ConnectServer doesn't distribute connections correctly | Balancing configured as HTTP instead of TCP | Use TCP mode (stream/HAProxy) for the ConnectServer |
| One instance always more overloaded than the others | Simple round-robin algorithm with irregular load | Switch to least_conn or least-active-connection balancing |
| Lag persists even after balancing the site | The real bottleneck is the GameServer/database, not the site | Investigate the GameServer and database separately |
Load balancer configuration checklist
- Components correctly identified (what can and can't be balanced).
- Multiple site/API instances configured and tested individually.
- Balancer (Nginx/HAProxy) configured with an appropriate algorithm (least_conn).
- Health checks configured for automatic removal of failed instances.
- Shared session (Redis/database) configured, if applicable.
- Dedicated TCP balancing for the ConnectServer, if needed.
- Load test performed before releasing to production.
- Continuous monitoring of requests, response time, and failures per instance.
With the connection layer and auxiliary services ready for access spikes, it's worth reviewing your environment's baseline setup — check the MU Online server setup tutorial to confirm the GameServer and database are also sized for your community's growth.
Frequently asked questions
Does a load balancer also work for the GameServer, or only for the site/ConnectServer?
The main GameServer (the game world) usually isn't balanced the same way, because it keeps every player's session state in a single process. What's easier to balance are the ConnectServer (authentication/initial routing), the site, and auxiliary APIs.
Do I need multiple physical servers to use a load balancer?
Not necessarily at first. You can run multiple ConnectServer or site instances in separate containers/processes on the same robust VPS, and use the load balancer to distribute between them, migrating to separate physical machines as growth demands it.
Is Nginx enough, or do I need a dedicated load balancer (HAProxy, etc)?
Nginx already handles most cases well for a small-to-medium MU server, both for the site and for ConnectServer proxying. HAProxy comes into play when you need more advanced layer-4 (TCP) balancing features with high performance.
How do I know if I really need a load balancer?
Clear signs are: login lag during peak hours even with normal CPU/RAM on the application, a connection queue on the ConnectServer, or traffic spikes on the site slowing down the admin panel's response. If your base is still small and stable, simpler optimizations may suffice before this step.
Does a load balancer fix lag inside the game world itself (maps)?
Not directly. Lag inside the world is usually a limitation of the GameServer/map itself (too many players on the same map, too many monsters, heavy scripts). A load balancer helps at the connection layer and auxiliary services, it doesn't replace optimizing the emulator itself.