Vertical Scaling Fails in Production

Updated on Apr 17, 2026
Mila H
11 MINS READ
Table of Contents
Why Vertical Scaling Fails

Vertical scaling, which is also known as scaling up, involves increasing the resources of a single server, including CPU, RAM, and storage, to handle growing workloads. Even though it is a simple way and can boot performance quickly, vertical scaling will eventually hit limits in real production systems. This guide explains why vertical scaling fails, how to detect the warning signs early, and what architectural changes to make next.

If you're running on dedicated servers or VPS infrastructure, PerLod Hosting scaling solutions can help you transition smoothly.

Why Vertical Scaling Fails?

Vertical scaling usually works at the beginning, but in production, this approach hits hard limits and creates new risks, which is why vertical scaling fails over time. Whether you're on a VPS or dedicated server, understanding these limits helps you scale smarter.

Here are the most common reasons that vertical scaling fails:

1. Hardware and Physical Limitations: Every server has a maximum limit. Cloud servers can only get so big, and physical machines can only take a certain number of CPUs, RAM, and disks. Once you hit that limit, you can’t scale up anymore.

Key limitations include:

  • CPU limit: Even the biggest cloud servers only offer a maximum number of vCPUs, so you can’t add more CPU.
  • Memory limit: Physical machines can only hold so much RAM because the motherboard has a limited number of slots.
  • Storage limit: Disks, even NVMe, and shared storage can only deliver a certain amount of IOPS and bandwidth, and you can’t push past that.
  • Network limit: Network cards have a top speed, so at some point, the network becomes the bottleneck instead of CPU or RAM.

2. Single Point of Failure (SPOF): Putting everything on one server is risky. If that single machine goes down, because of hardware problems, an OS crash, or the app crashing, your whole service goes offline.

  • Hardware failure of a CPU, memory module, or motherboard brings down all services.
  • Software bugs or kernel issues affect the entire system simultaneously.
  • Maintenance becomes harder because you often have to take the whole server offline to upgrade or fix it, which means full downtime. That downtime can affect SLA commitments.

3. Performance Bottlenecks and Diminishing Returns: Vertical scaling often creates new bottlenecks while solving old ones. Adding CPU cores won't help if disk I/O or network bandwidth becomes the limiting factor.

Common bottleneck scenarios include:

  • Faster CPU, same slow disk or network: The CPU ends up waiting because data can’t arrive fast enough.
  • More RAM, same system speed: Memory is bigger, but data still moves slowly inside the machine.
  • Multi-CPU overhead: The server wastes time keeping all CPUs in sync.

4. High Cost and Economic Inefficiency:

Vertical scaling gets expensive fast. As you move to bigger premium servers, the price usually grows much faster than the performance, so you pay a lot more for a smaller gain.

5. Mandatory Downtime for Upgrades: Upgrading a single server usually means downtime. For physical hardware, you often must shut the machine down to add or replace CPU or RAM, and in the cloud, scaling up requires stopping and restarting the instance, so users see an interruption.

Typical downtime ranges include:

  • Physical CPU and RAM upgrade: Around 30 minutes to 2 hours.
  • Cloud instance resize: 5 to 15 minutes of unavailability.
  • Storage migration: hours to days, depending on how much data you move.

6. Vendor Lock-in and Migration Complexity: When you keep scaling up, you may end up relying on special and expensive hardware or a specific cloud big instance type. That can make it harder to switch providers or move your workload later, because the setup and performance tuning don’t transfer cleanly.

Detect Vertical Scaling Failures

At this point, you can detect warning signs that vertical scaling fails, like rising CPU load, memory pressure, slow disk I/O, and longer response times, before the server hits its hard limit and the whole service becomes unstable.

1. CPU Monitoring Commands:

You can use the top command for real-time process monitoring.

Bash
# Basic usagetop # Show CPU cores individuallytop -1 # Filter by usertop -u apache # Batch mode for scriptingtop -b -n 1 | head -20

Key signs of CPU limits include:

  • CPU usage stays high: User and system CPU are around 80% or more most of the time.
  • Load is too high: Load average is higher than the number of CPU cores for long periods.
  • High I/O wait: %wa is high, which usually means the CPU is waiting on disk or network, not doing real work.

You can use the htop command for enhanced visual monitoring:

Bash
# Install on Ubuntu/Debiansudo apt install htop # Install on RHEL/CentOSsudo dnf install htop # Run with tree viewhtop -t

Also, you can use the vmstat command for system-wide resource statistics:

Bash
# Update every 2 secondsvmstat 2 # 10 iterations with 1-second delayvmstat 1 10 # Show disk statisticsvmstat -d 2

Essential signs of limitations include:

  • r (run queue) > CPU cores
  • us + sy > 80% for a while
  • wa > 15%
  • id (idle) < 10%

2. Memory Monitoring Commands:

You can use the free command for memory utilization:

Bash
# Human-readable outputfree -h # Show in megabytesfree -m # Continuous monitoringwatch -n 2 free -h

Warning signs that vertical scaling fails include:

  • Available memory < 10% of total
  • Swap usage > 0
  • Buffer and cache are not reclaiming under load

You can use the vmstat command with the -s flag for memory indicators:

Bash
vmstat -s

In the output, watch for:

  • The swap used is increasing over time.
  • The active memory is approaching the total memory.

3. Disk I/O Monitoring:

Use the iostat command to display I/O device statistics:

Bash
# Install sysstat packagesudo apt install sysstat  # Ubuntu/Debiansudo dnf install sysstat    # RHEL/CentOS # Show extended statistics every 2 secondsiostat -x 2 # Display in megabytesiostat -xm 2

Warning signs that vertical scaling fails include:

  • %util > 80% for extended periods
  • await > 20ms
  • avgqu-sz > 1

Also, you can use the df command to monitor disk space usage:

Bash
# Human-readabledf -h # Show inodesdf -i # Monitor specific filesystemwatch -n 5 'df -h /var/lib/mysql'

4. Network Monitoring Commands:

To monitor network connections, you can use the netstat command:

Bash
# Show listening portsnetstat -tuln # Show connection statesnetstat -an | grep ESTABLISHED | wc -l # Monitor continuouslywatch -n 2 'netstat -an | grep :80 | wc -l'

Use the ss command to display modern socket statistics:

Bash
# Show TCP connectionsss -t -a # Show memory usage per socketss -m

For real-time bandwidth monitoring, you can use the iftop command:

Bash
# Installsudo apt install iftop  # Ubuntu/Debiansudo dnf install iftop    # RHEL/CentOS # Monitor specific interfacesudo iftop -i eth0

5. Application-Level Detection:

Thread Pool Monitoring: The case study showed how vertical scaling broke thread pool behavior. You can use the commands below to monitor thread utilization:

Bash
# Count Java threadsjcmd <PID> Thread.print | grep -c "Thread" # Monitor thread pool sizesjstat -gcutil <PID> 1000 # Show native threadsps -eLf | grep java | wc -l

Garbage Collection Pressure: When you give a Java service more memory, it can still run into GC problems, because the JVM may spend more time cleaning up a larger heap. To monitor and enable GC logs, use the commands below:

Bash
# Monitor GC activityjstat -gc <PID> 1000 # Enable GC loggingjava -Xlog:gc*:file=/var/log/gc.log:time,level,tags

Warning signs include:

  • FGC (Full GC) frequency is increasing.
  • FGCT (Full GC time) exceeding 5% of total time.

Connection Pool Exhaustion:

Bash
# Monitor MySQL connectionsmysql -e "SHOW STATUS LIKE 'Threads_connected';" # Monitor PostgreSQLpsql -c "SELECT count(*) FROM pg_stat_activity;"

Automated Detection Script: You can create a comprehensive monitoring script with:

Bash
#!/bin/bash# vertical_scaling_limits.sh THRESHOLD_CPU=80THRESHOLD_MEM=90THRESHOLD_IO=80THRESHOLD_LOAD=0.8 echo "=== Vertical Scaling Limit Detection ==="echo "Timestamp: $(date)" # CPU checkCPU_IDLE=$(vmstat 1 2 | tail -1 | awk '{print $15}')CPU_USAGE=$((100 - CPU_IDLE))echo "CPU Usage: ${CPU_USAGE}%"if [ $CPU_USAGE -gt $THRESHOLD_CPU ]; then    echo "WARNING: CPU usage exceeds threshold"fi # Memory checkMEM_INFO=$(free | grep Mem)MEM_TOTAL=$(echo $MEM_INFO | awk '{print $2}')MEM_USED=$(echo $MEM_INFO | awk '{print $3}')MEM_PERCENT=$((MEM_USED * 100 / MEM_TOTAL))echo "Memory Usage: ${MEM_PERCENT}%"if [ $MEM_PERCENT -gt $THRESHOLD_MEM ]; then    echo "WARNING: Memory usage exceeds threshold"fi # Load average checkCORES=$(nproc)LOAD1=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')LOAD_THRESHOLD=$(echo "$CORES * $THRESHOLD_LOAD" | bc)echo "Load Average (1min): $LOAD1 (Threshold: $LOAD_THRESHOLD)"if (( $(echo "$LOAD1 > $LOAD_THRESHOLD" | bc -l) )); then    echo "WARNING: Load average exceeds threshold"fi # Disk I/O checkif command -v iostat >/dev/null; then    IO_UTIL=$(iostat -x 1 2 | awk '/^sda/ {print $NF}' | tail -1)    echo "Disk I/O Utilization: ${IO_UTIL}%"    if (( $(echo "$IO_UTIL > $THRESHOLD_IO" | bc -l) )); then        echo "WARNING: Disk I/O exceeds threshold"    fifi echo "======================================="

Kubernetes-Specific Detection: For containerized environments, you can use Vertical Pod Autoscaler (VPA) recommendations:

Bash
# Check VPA recommendationskubectl describe vpa <vpa-name> # View current vs recommended resourceskubectl get vpa <vpa-name> -o yaml # Monitor pod resource usagekubectl top pods --sort-by=cpu # Watch for OOMKilled eventskubectl get events --field-selector reason=OOMKilled

VPA status signs of limits:

YAML
status:  recommendation:    containerRecommendations:    - containerName: app      lowerBound:        cpu: 500m        memory: 1Gi      target:        cpu: 2        memory: 4Gi      upperBound:        cpu: 4        memory: 8Gi

When the target approaches your instance limits, vertical scaling is exhausted.

Next Steps After Vertical Scaling Hits Limits

Once scaling up hits hardware limits, becomes too risky, or stops improving performance, the next step is to change the architecture, not buy a bigger server. These are the common paths teams can take after vertical scaling fails:

Use Horizontal Scaling Architecture

You can distribute the workload across multiple servers instead of upgrading one. The implementation steps include:

Load Balancer Setup:

Bash
# Install HAProxy on Ubuntusudo apt install haproxy # Basic configuration /etc/haproxy/haproxy.cfgcat > /etc/haproxy/haproxy.cfg << 'EOF'global    daemon    maxconn 4096 defaults    mode http    timeout connect 5000ms    timeout client 50000ms    timeout server 50000ms frontend web_frontend    bind *:80    default_backend web_backend backend web_backend    balance roundrobin    server web1 10.0.1.10:80 check    server web2 10.0.1.11:80 check    server web3 10.0.1.12:80 checkEOF # Restart HAProxysudo systemctl restart haproxy

Modify applications to avoid local state:

Python
# Bad: Local file storagedef save_upload(file):    with open(f"/tmp/{file.name}", "wb") as f:        f.write(file.data) # Good: Object storagedef save_upload(file):    s3_client.upload_fileobj(        file.data,         "my-bucket",         f"uploads/{file.name}"    )

Database Read Replicas:

SQL
-- MySQL read replica configurationCHANGE MASTER TOMASTER_HOST='master.example.com',MASTER_USER='replica',MASTER_PASSWORD='password',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=107; START SLAVE;

Session Management: Use external session stores:

Bash
# Redis for session storagesudo apt install redis-server # Configure PHP to use Redis for sessionsecho "session.save_handler = redis" >> /etc/php/8.1/apache2/php.iniecho "session.save_path = tcp://127.0.0.1:6379" >> /etc/php/8.1/apache2/php.ini

You can learn more about horizontal scaling by checking this guide on Horizontal Scaling Strategies.

Use Hybrid Scaling Strategy

If vertical scaling fails, you can use vertical and horizontal scaling for optimal efficiency.

Vertical Optimization:

YAML
# Right-size existing instances# Use VPA recommendations to optimize resource requestskubectl apply -f - << 'EOF'apiVersion: autoscaling.k8s.io/v1kind: VerticalPodAutoscalermetadata:  name: app-vpaspec:  targetRef:    apiVersion: apps/v1    kind: Deployment    name: app  updatePolicy:    updateMode: "Auto"EOF

Horizontal Expansion:

YAML
# Horizontal Pod Autoscalerkubectl apply -f - << 'EOF'apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata:  name: app-hpaspec:  scaleTargetRef:    apiVersion: apps/v1    kind: Deployment    name: app  minReplicas: 2  maxReplicas: 10  metrics:  - type: Resource    resource:      name: cpu      target:        type: Utilization        averageUtilization: 70EOF

Here is a suggestion for teams that usually want to move from vertical scaling to horizontal scaling as the user base grows:

User Base Scaling Approach Timeline
< 100K users Vertical only 1-3 months
100K-1M users Hybrid (Vertical → Horizontal) 3-6 months
> 1M users Horizontal mandatory 6-12 months

To learn more about Hybrid architecture, check this guide on building Hybrid hosting architecture.

Use Microservices Decomposition

Microservices decomposition means turning one big app into smaller services that can run and scale on their own. This helps after vertical scaling fails, because you can scale only the busy parts instead of upgrading one huge server.

Decomposition Strategy:

Bash
# Example: Extract user service from monolith# 1. Create new service repositorymkdir user-service && cd user-service # 2. Initialize with

Conclusion

Vertical scaling is quick and simple, so teams start with it, but it will eventually hit limits, including hardware limits, higher costs, downtime risk, and hidden design issues. If you monitor CPU, memory, disk I/O, and app behavior early, you can plan before things break.

When vertical scaling fails, the fix isn’t a bigger server; it’s changing the architecture. Most teams move to horizontal scaling, a hybrid method, or microservices based on growth.

We hope you enjoy this guide on vertical scaling fails. Subscribe to our X and Facebook channels to get the latest articles.

For further reading:

Horizontal vs Vertical Scaling in Dedicated Servers.

Watch for sustained high CPU, memory pressure, slow disk I/O, or increasing error rates. These are the main warning signs that vertical scaling fails.

If your app stores data locally or keeps session state in memory, you'll need to fix that. But with a load balancer and a shared database, you can often scale horizontally with minimal code changes.

No. Microservices add complexity. Use them only if different parts of your app have very different load patterns. For many small apps, horizontal scaling on a single codebase is simpler and enough.