Skip to content

DigitalOcean Droplet Vertical Resize (doctl)

When a droplet is hitting a CPU or memory ceiling but you want to keep it on DigitalOcean (e.g. a region with no alternative provider), the fix is a vertical resize to a larger plan. This is a short, planned maintenance task — one power-off/on cycle — but the choice of resize flavor matters a lot for whether you can undo it later.

Background

This procedure was written from the teelia Phase 8 resize (2026-06-18), where the UK droplet hosting temptedparadise.com was lifted from s-1vcpu-2gb to s-2vcpu-4gb to end recurring single-vCPU CPU spikes and chronic memory pressure. See the teelia server note's incident log for the worked example and before/after numbers.

The key decision: which resize flavor

DigitalOcean offers two kinds of resize, and only one is reversible. Choose deliberately — this is the single most important decision.

Flavor What changes Reversible? Use when
CPU + RAM only (doctl ... resize --size <slug>, no --resize-disk) vCPU + RAM up to the new plan; disk stays at its current size ✅ Yes — you can downsize to a smaller plan later The bottleneck is CPU/RAM and current disk is comfortably under the target plan's disk
CPU + RAM + disk (--resize-disk) vCPU + RAM + disk grows to the new plan's disk ❌ No — disk can never shrink, so the droplet can never be downsized again You genuinely need the bigger disk and accept permanent lock-in

Reversibility is backwards from what people assume

The CPU+RAM-only resize is the reversible one (it leaves the disk alone). The disk resize is the permanent / one-way one. Default to CPU+RAM-only unless you specifically need more disk — check df -h / first; if you're well under the target plan's disk, you don't.

You can always do a CPU+RAM-only resize now and a separate disk resize later if growth demands it. You cannot undo a disk resize.

Procedure

All commands run from a host with doctl authenticated (doctl account get to confirm). Replace <ID> with the droplet ID (doctl compute droplet list --format ID,Name,SizeSlug).

1. Pre-flight

# Confirm the droplet and record its current SizeSlug — that is your rollback target
doctl compute droplet get <ID> --format ID,Name,Status,Memory,VCPUs,Disk,SizeSlug

# Record live vitals for before/after comparison (over Tailscale if SSH is tailnet-only)
ssh root@<tailscale-ip> 'nproc; free -h; df -h /; \
  systemctl is-active <your services>; uptime'

2. Power off + snapshot (rollback insurance)

doctl compute droplet-action power-off <ID> --wait
doctl compute droplet-action snapshot <ID> --snapshot-name "<host>-pre-resize-$(date -u +%F)" --wait
doctl compute snapshot list --format ID,Name,Created | grep pre-resize

A snapshot of a partly-full 25 GB disk takes a few minutes. If --wait outlives your shell's timeout, start it without --wait and poll: doctl compute action get <action-id> --format Status.

3. Resize (CPU + RAM only) + power on

# NO --resize-disk → reversible
doctl compute droplet-action resize <ID> --size <target-slug> --wait
doctl compute droplet-action power-on <ID> --wait
doctl compute droplet get <ID> --format Name,Status,Memory,VCPUs,Disk,SizeSlug

4. Verify

ssh root@<tailscale-ip> 'nproc; free -h; df -h /; \
  systemctl is-active <your services>; systemctl --failed --no-legend; swapon --show'
# Plus an app-level smoke test, e.g.:
curl -s -o /dev/null -w "HTTP %{http_code} | TTFB %{time_starttransfer}s\n" https://<site>/ --max-time 60

Expect the new nproc / free -h totals, all services active, no failed units, and the app responding.

Rollback

Because the resize was CPU+RAM-only, you have two independent rollback paths:

# A) Downsize again (reversible resize, fast):
doctl compute droplet-action power-off <ID> --wait
doctl compute droplet-action resize <ID> --size <original-slug> --wait
doctl compute droplet-action power-on <ID> --wait

# B) Restore from the pre-resize snapshot (for a boot/data problem):
doctl compute droplet-action restore <ID> --image-id <snapshot-id> --wait

Sweep the pre-resize snapshot ~30 days after the change once stability is proven, so you stop paying for it.

Gotchas

  • --wait blocks. A snapshot can run several minutes; if your shell/tooling has a timeout, the underlying DO action still completes server-side — re-query doctl compute droplet get <ID> rather than re-issuing the action.
  • Status lag. Immediately after power-off completes, droplet get may still read active for a moment. Confirm Status: off before resizing.
  • SSH alias may not resolve. If the fleet uses Tailscale MagicDNS and the node was renamed, an old hostname alias can fail to resolve — fall back to the Tailscale IP (ssh root@100.x.x.x). SSH is typically tailnet-only, so the public IP won't accept connections.
  • Disk % gate. Confirm df -h / is under the target plan's disk before choosing CPU+RAM-only — DO requires the existing disk to fit the new plan (it always will when going up).
  • Post-resize tuning. App-level caps you set for the smaller box (e.g. PHP-FPM pm.max_children, Apache mpm_event MaxRequestWorkers) don't auto-scale — raise them to use the new headroom.