Load Balancing for Your Own Site: Multi-Instance Deployment, Health Checks, Session Persistence, and Failover
1. When you need load balancing, and when it’s overkill
Load balancing isn’t something you should put in from day one. Under 5,000 daily visits, a single 2-core 4GB VPS running Nginx+PHP+MySQL is plenty, and spending a few hundred bucks on an LB just adds complexity. There are three real signals that you need multiple instances: a single machine’s CPU stays above 60%, you have to take the site down to deploy, or you’ve already lost inquiries because one machine failed. Meet two of the three, and then add an LB.
The typical upgrade path for your own site is: a single VPS running compose (under 10,000 daily visits) → move MySQL to its own machine (30,000 daily visits) → run 2 app instances plus a cloud LB (50,000 daily visits) → 3 to 5 app instances plus autoscaling (over 200,000 daily visits). Don’t skip steps. Put a CDN in front of the LB to absorb static traffic first; that can cut origin load in half. See CDN global acceleration.
| Stage | Deployment setup | App instances | Daily traffic reference | Added monthly cost |
|---|---|---|---|---|
| Startup phase | Single VPS + compose | 1 | <10,000 | 0 |
| Growth Stage | 1 app instance + 1 separate DB machine | 1 | 10,000 to 50,000 | +150 yuan |
| Steady stage | 2 app instances + cloud LB | 2 | 50,000 to 200,000 | + about 100 yuan for the LB |
| Peak periods | 3 to 5 app instances + autoscaling | 3 to 5 | >200,000 | + billed by the hour |
2. L4 or L7: which layer should your own site pick?
Load balancing splits into Layer 4 (L4, forwarding by TCP/IP) and Layer 7 (L7, forwarding by HTTP path). L4 performs better and is simpler to configure, but it can only forward by port. L7 can route by domain, path, and header, and it can also handle canary releases and HTTPS offloading. Pick L7 for your own site, because you need to split traffic by path: static files go to the CDN, the API goes to the app, and the admin panel goes to a separate set of instances.
Choosing a load balancer by cloud provider: AWS uses ALB (Application Load Balancer), Alibaba Cloud uses SLB or ALB, and Tencent Cloud uses CLB. They overlap in features, and pricing starts at 100 to 200 yuan per month. One rule of thumb: if all you need is round-robin across two servers, use the cheapest Layer 4 LB. If you need path-based routing, canary releases, or domain-based traffic splitting, go with a Layer 7 LB.
| Comparison | L4 TCP load balancing | L7 HTTP load balancing |
|---|---|---|
| Routing basis | IP + port | Domain / path / header / cookie |
| Performance | Very high, microsecond latency | Slightly lower, millisecond latency |
| HTTPS offloading | Not supported | Supported, certificate stored on the LB |
| Canary release | Not supported by traffic ratio | Supported by weight-based traffic splitting |
| Best for | Simple round-robin across two servers | Multi-path routing, canary releases |
| Monthly cost (reference) | About 80 to 120 yuan | About 150 to 250 yuan |
3. Health checks: how to configure them without killing healthy nodes
Health checks are the core of any LB. Configure them too loosely and dead servers keep receiving traffic; configure them too aggressively and an app that’s just slow to start gets pulled out, triggering a cascade failure. The standard setup for a standalone site: check path /health (returns only 200, no database query, so a slow DB doesn’t bleed into the LB), interval of 10 seconds, remove after 3 consecutive failures, restore after 2 consecutive successes.
The /health endpoint must stay lightweight. A common mistake is having it run a MySQL connection check, so when the database hiccups, the LB pulls every app instance and the whole site returns 503. The right approach: /health returns only the process liveness status, while database health goes through a separate /health/db endpoint that’s monitored but doesn’t affect removal decisions. For monitoring and alert thresholds, see Site monitoring and alerts.
| Spec | Recommended value | Description | Common mistake |
|---|---|---|---|
| Check path | GET /health | No DB query, returns only 200 | DB query pulls all nodes |
| Check interval | 10 seconds | Balances sensitivity and false positives | 1 second is too aggressive |
| Timeout | 5 seconds | Exceeding it counts as one failure | 1 second is too short |
| Unhealthy threshold | 3 consecutive failures | Remove node | Remove after 1 failure |
| Healthy Threshold | 2 consecutive successes | Restore to pool | Restore after 1 success |
| Response code | 200 counts as healthy | Any other code counts as a failure | Treating 503 as healthy |
4. Session persistence: avoid it if you can
When people first run multiple instances, their first reaction is often “if a user logs in on server A, won’t the next request hitting server B log them out?” So they turn on session persistence. It’s a shortcut now that you pay for later. The problem with session persistence: once server A goes down, every user stuck to it gets logged out, which cancels out half the point of failover.
The right approach is to move sessions out of app memory. Use Redis for sessions in PHP, stateless JWT tokens in Node, and keep all login state in one shared store. That way any app instance can handle any user’s request, and losing any single server doesn’t affect logged-in users. Only two cases justify session persistence: a legacy system you can’t change, or a third-party plugin that hard-depends on local sessions.
| Session approach | Scalability | Failure impact | Migration cost | Recommendation |
|---|---|---|---|---|
| Local file sessions | Poor, sticky sessions required | One server down = users logged out | Low | Not Recommended |
| Centralized Redis sessions | Good | Seamless failover | Medium, just change the config | Recommended |
| Stateless JWT tokens | Best | Seamless failover | High — requires auth changes | Recommended for new projects |
| LB cookie stickiness | Bad | If the pinned server dies, users drop off | Low — just flip a switch | Temporary stopgap only |
5. Failover: how to prove your LB actually switches
Configuring the LB doesn’t mean failover works. Many teams never kill a server on purpose after launch, and only find out the LB has no health check or the security group isn’t open when something really dies. Run a failover drill before launch: in production (or staging), manually kill app A and watch whether the LB moves traffic to B within 30 seconds and whether users notice anything.
Another common trap: both app instances sit on the same physical host or the same power rack, so a rack outage takes down both at once. Deploy across at least two availability zones (AZs), or at minimum two different hosts. For the full methodology on backup and recovery drills, see Backup and recovery drills for your site.
Example: a B2B site ran WordPress on a single VPS. One night the host went down for maintenance and lost power, the site was offline for 4 hours, and 3 inquiries from European customers were missed. Afterward they added a second app instance behind a cloud LB, moved sessions to Redis, and deployed across AZs. At the next maintenance window, instance B picked up all traffic automatically and neither customers nor sales noticed. The extra 300 yuan a month for the instance cost far less than the inquiries lost in one outage.
| Drill scenario | Actions | Expected result | If it fails |
|---|---|---|---|
| Kill the app process | systemctl stop app | LB removes it within 30 seconds, traffic shifts to B | Check the health check path |
| Cut the app’s network | Block the port with iptables | Removed within 10 seconds | Check the security group allows the LB subnet |
| Reboot the server | reboot | Rejoins the pool automatically after boot | Check the startup script and health |
| Database hiccup | Inject a slow query | App is not wrongly removed | health must not query the DB |
| Shift all traffic to B | Set B’s weight to 100 | B handles it alone for 5 minutes | Load-test B’s capacity |
FAQ
How much daily traffic before you need a load balancer?
Should you enable session persistence at all?
Why shouldn’t health checks query the database?
Can I put both instances on the same physical machine?
How much do cloud load balancers cost per month?
How do I verify failover actually works?
Final thoughts
For a standalone site, half the value of load balancing is handling traffic and the other half is surviving failures. Most people can calculate the first half; the second half gets ignored. One extra app instance buys you “a machine goes down and users never notice” – and in B2B inquiry scenarios, that’s money.
Three steps to get there. First, make the app stateless and move sessions into Redis. Second, spin up a second instance, put it behind the cloud LB, and configure health checks. Third, run a kill-the-process drill and confirm traffic shifts within 30 seconds. Only after all three steps is a multi-instance architecture truly usable. For the monitoring and alerting that goes with it, see Site monitoring and alerts. For the advanced setup with auto scaling, see Auto Scaling.
One line worth remembering: load balancing isn’t about splitting traffic. It’s about making sure that when any machine goes down, neither users nor inquiries notice a thing.
