How to Set Up VPS Auto-Scaling with Cloud-init and Custom Scripts

Auto-scaling is the holy grail of VPS infrastructure — adding capacity on demand without manual intervention. While cloud platforms offer native auto-scaling groups, you can build a surprisingly robust auto-scaling system for any VPS provider using cloud-init for initialization and custom scripts for orchestration. This guide walks through a practical implementation that works with any provider that supports cloud-init (most modern ones do).

What You’ll Need

  • A VPS provider that supports cloud-init user data (check our VPS provider comparison table for compatible options)
  • A separate “controller” VPS that monitors load and provisions new instances
  • API access to your VPS provider (for programmatic provisioning)
  • A shared object store or NFS server for session/data persistence

Step 1: Build a Cloud-Init Template

Cloud-init runs on first boot and configures the server automatically. Here’s a template that installs your application stack and joins the server to a load balancer pool:

#cloud-config
package_update: true
package_upgrade: true

packages:
  - nginx
  - nodejs
  - git
  - curl
  - fail2ban

runcmd:
  - git clone https://github.com/your-org/app-deploy.git /opt/app
  - cd /opt/app && npm install
  - systemctl enable app.service
  - systemctl start app.service
  - |
    curl -X POST -H "Content-Type: application/json" \
      -d '{"ip": "'$(hostname -I | awk '{print $1}')'", "name": "'$(hostname)'"}' \
      https://controller.internal/register

write_files:
  - path: /etc/nginx/sites-available/app
    content: |
      server {
        listen 80;
        server_name _;
        root /opt/app/public;
        location / { proxy_pass http://localhost:3000; }
      }
  - path: /etc/nginx/sites-enabled/app
    content: ""
  - path: /opt/app/.env
    content: |
      DB_HOST=db.internal.cluster
      REDIS_HOST=redis.internal.cluster

This template installs dependencies, clones your application, starts the service, and registers with a central controller — all without any manual SSH session.

Step 2: The Controller Script

On your controller VPS, run a script that checks CPU load or request queue depth every 30 seconds. When load exceeds a threshold, provision a new instance via the provider API with the cloud-init template:

#!/bin/bash
THRESHOLD=75          # Scale up when CPU > 75%
COOLDOWN=120          # Seconds between scale events
MAX_INSTANCES=10
INSTANCES_FILE=/tmp/instances.json

scale_up() {
  curl -s -X POST \
    -H "Authorization: Bearer $PROVIDER_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "region": "us-east",
      "plan": "professional-m",
      "image": "ubuntu-24-04",
      "user_data": "'$(base64 -w0 cloud-init.yaml)'"
    }' \
    "https://api.provider.com/v2/instances" | tee -a /var/log/autoscale.log
}

monitor() {
  LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
  INSTANCE_COUNT=$(curl -s "$PROVIDER_API/instances" | jq length)

  if (( $(echo "$LOAD > $THRESHOLD" | bc -l) )) && \
     (( INSTANCE_COUNT  COOLDOWN )); then
    scale_up
    LAST_SCALE=$(date +%s)
  fi
}

while true; do
  monitor
  sleep 30
done

Step 3: Load Balancer Integration

Your load balancer (HAProxy, Nginx, or a managed LB) needs to dynamically register new backends. The cloud-init template above calls /register on the controller. The controller then updates the load balancer config:

#!/bin/bash
# Called by the controller when a new instance registers
INSTANCE_IP=$1
INSTANCE_NAME=$2

# Add to HAProxy backend
echo "    server $INSTANCE_NAME $INSTANCE_IP:3000 check" >> /etc/haproxy/backends.cfg
haproxy -f /etc/haproxy/haproxy.cfg -sf $(cat /var/run/haproxy.pid)

Step 4: Scale-Down Logic

Scaling down is trickier — you need to drain connections before terminating. Add a /drain endpoint to your application that returns a 503 during shutdown, and in the controller, deregister the instance from HAProxy, wait 60 seconds (for in-flight requests to complete), then call the provider’s API to delete the instance.

Testing Your Setup

Use a load testing tool like hey or wrk to simulate traffic:

hey -n 10000 -c 100 http://your-app.com/

Watch the controller logs — new instances should spin up within 60–90 seconds of crossing the threshold.

Choosing the Right VPS Provider for Auto-Scaling

Not all VPS providers are equal when it comes to fast provisioning and API reliability. You need a provider with:

  • Sub-60-second provisioning times
  • Reliable cloud-init support
  • A well-documented provisioning API
  • NVMe storage (for fast application boot)

To find the best fit, compare VPS providers on our performance comparison table — we benchmark provisioning speed, API reliability, and cloud-init compatibility across major providers.

Leave a Reply