Skip to content
Innovialabs

Horizontal vs Vertical Scaling: How to Scale an Application

Basics3 min

In short

Vertical scaling means a bigger server; horizontal scaling means more servers behind a load balancer. Scaling up is the easier start, but long-term growth and fault tolerance require a stateless app ready to scale out.

The short answer

Vertical scaling (scale up) means giving one server more resources: more CPU, more memory, faster disks. No code changes are needed.

Horizontal scaling (scale out) means running several copies of your application on different servers and spreading traffic between them with a load balancer. It requires some architectural preparation, but it has almost no ceiling and gives you fault tolerance.

Side-by-side comparison

CriterionVerticalHorizontal
Code changesNoneRequired: statelessness, shared sessions
CeilingThe largest server availablePractically none
Fault toleranceSingle point of failureOne node failing is not critical
Downtime when growingOften a restartNodes are added without stopping
Operational complexityLowHigher: load balancer, node monitoring
CostPowerful hardware gets disproportionately expensiveMany cheap nodes, more maintenance

Limits of scaling up

  • Physical ceiling. At some point there is no bigger server, or it costs far more than it is worth.
  • Single point of failure. If the server goes down, everything goes down.
  • Downtime on upgrades. Changing plans or hardware usually means a reboot.
  • Not everything gets faster. If the app is bound by single-threaded code or database locks, extra cores help little.

Still, scaling up is a sensible start: it is fast, cheap to maintain and covers the growth of most small projects.

What makes an app horizontally scalable

The key rule: any instance must be able to handle any request. To get there:

  • Stateless service. Do not keep user state in process memory.
  • Shared sessions. Store sessions in Redis, a database or signed tokens (such as JWT), not in one server's memory.
  • Files in external storage. Put user uploads in object storage (S3-compatible), not on a local disk.
  • Background jobs through a queue. A cron job on every node runs the task N times; use a queue or a distributed lock.
  • Shared cache. A local cache on each node leads to inconsistent data.
  • Health checks. The load balancer needs an endpoint like /health to know which node is alive.

A minimal load-balancing example in nginx:

upstream app {
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
}

server {
    listen 80;
    location / {
        proxy_pass http://app;
    }
}

What about the database

The application scales out easily; the database is harder. The usual sequence:

  1. Scale the database server up and optimize queries and indexes.
  2. Move reads to read replicas.
  3. Cache hot data.
  4. Only when truly necessary, use sharding — it adds significant complexity.

When to switch to scaling out

  • Load regularly hits one server's limits, and the next tier is disproportionately expensive.
  • Downtime is unacceptable and you need fault tolerance.
  • Traffic fluctuates a lot, so adding nodes on demand (autoscaling) pays off.
  • You need to update the app without stopping it (rolling deploys).

Common mistakes

  • Scaling out without removing in-memory state, so users keep getting logged out.
  • Adding servers when the real bottleneck is slow database queries.
  • Skipping monitoring and not knowing what is actually saturated: CPU, memory, disk or network.

FAQ

With vertical scaling and optimizing code and queries. But build the app stateless from day one, so moving to several servers later does not require a rewrite.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.