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¶
--waitblocks. A snapshot can run several minutes; if your shell/tooling has a timeout, the underlying DO action still completes server-side — re-querydoctl compute droplet get <ID>rather than re-issuing the action.- Status lag. Immediately after
power-offcompletes,droplet getmay still readactivefor a moment. ConfirmStatus: offbefore 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, Apachempm_event MaxRequestWorkers) don't auto-scale — raise them to use the new headroom.
Related¶
- VPS Migration Baseline Checklist
- teelia — worked example (Phase 8 resize, 2026-06-18)
- Hetzner-Migration-Status — Phase 8 tracker