A VPS that had been serving our app comfortably started returning five-second responses during the afternoon traffic peak. The CPU graph looked fine; the access log eventually showed that PHP requests were waiting on a database query.
In this guide, I’ll walk through a repeatable way to profile a slow website on a VPS: measure the request, check the host, find the expensive layer, and verify the fix.
Start with one slow request
Don’t begin by changing server settings. First, establish what “slow” means and whether the delay is consistent.
From your own machine, run:
curl -sS -o /dev/null \
-w 'DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n' \
https://example.com/
Replace example.com with your site. Run it a few times, including against a page that users report as slow. The timings help separate DNS, connection setup, TLS, and waiting for the first byte. A high TTFB can point to server-side work, but it can also include network latency, a cache miss, or a slow upstream service.
If you want a rough comparison between routes, test a static file as well:
curl -sS -o /dev/null \
-w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' \
https://example.com/health.txt
A fast static response and a slow dynamic page suggest different places to investigate than two equally slow responses. This is a clue, not a diagnosis.
Check the VPS while the problem is happening
SSH into the machine and look at resource pressure during a slow request. A quiet server after the incident may not show what caused it.
uptime
free -h
df -h
vmstat 1 5
uptime shows load averages, but load is not a CPU percentage. On a four-vCPU VPS, a load average near four means roughly four tasks were runnable or waiting on uninterruptible work on average; it doesn’t by itself tell you whether the CPU is saturated.
In vmstat, watch r (runnable tasks), si/so (swap activity), and wa (I/O wait). Sustained swap-in/swap-out or I/O wait deserves attention. Then inspect processes:
top
For disk latency and utilization, install sysstat if needed, then run:
iostat -xz 1 5
Package installation varies by distribution (sudo apt install sysstat on Debian/Ubuntu, for example). High disk utilization or elevated I/O wait can indicate storage contention, but confirm it against the workload before upgrading storage.
Check whether the server is running out of connections or accumulating them:
ss -s
A large connection count is not automatically a problem. Compare it with your web server’s worker limits and observe whether connections are stuck in states such as CLOSE-WAIT.
Measure application time in the access log
For Nginx, logging request duration and upstream duration is often the fastest way to separate web-server overhead from application time. Add a log format inside the http block in /etc/nginx/nginx.conf:
log_format timed '$remote_addr "$request" status=$status '
'request_time=$request_time '
'upstream_time=$upstream_response_time';
access_log /var/log/nginx/access.log timed;
Check and reload the configuration:
sudo nginx -t
sudo systemctl reload nginx
Watch the log while reproducing the slow request:
sudo tail -f /var/log/nginx/access.log
request_time is the time Nginx spends handling the request. upstream_response_time is the time spent waiting for an upstream such as PHP-FPM or an application server. If upstream time is close to total request time, investigate the app or its dependencies. If it’s much lower, look at Nginx, client behavior, or response transfer time.
One practical detail: upstream timing can contain multiple values when Nginx retries or contacts multiple upstreams. Don’t assume every log line maps to one clean application call.
Find the slow dependency
If the application is waiting on a database, inspect its slow-query logging or use the database’s query analysis tools. Avoid guessing from CPU usage alone: a query waiting on disk or a lock may not push CPU high.
For a PHP-FPM site, check the PHP-FPM service logs and pool settings. For a Node.js or Python app, inspect the application logs and add timing around database, cache, and external API calls. Record durations and request IDs, but don’t log credentials, session tokens, or full sensitive request bodies.
A useful pattern is to time each major dependency separately:
request total: 1.82s
database: 1.65s
cache: 0.01s
external API: 0.08s
That points toward a database investigation—not a bigger VPS. If the database is remote, include network latency in the diagnosis.
Reproduce carefully
A small load test can show whether latency climbs with concurrency, but don’t hammer a production site without permission and a safe test window. Start against staging or a controlled endpoint. For example, with hey installed:
hey -n 100 -c 5 https://example.com/health
This sends 100 requests with concurrency 5. It is a basic comparison, not a realistic traffic model. Check CPU, memory, and logs during the test, and stop if the system becomes unhealthy. Never assume that a test against /health represents the cost of a dynamic page.
Make one change, then measure again
Change one thing at a time: optimize a query, add an appropriate cache, fix a connection-pool limit, or resize the VPS. Repeat the same request and test conditions afterward. Compare median and tail latency where possible; a better average can hide a handful of painfully slow requests.
Two less-obvious lessons from practice:
- Check the clock on every machine involved. If the app, database, and reverse proxy have skewed clocks, correlating logs becomes misleading. Use NTP or the system’s time-synchronization service.
-
Don’t benchmark from only one network. A request that is slow from your laptop but fast from the VPS may point to DNS, routing, or a client-side issue. Running the same
curlfrom the VPS helps separate those cases.
When the VPS itself is the bottleneck
Only upgrade after you’ve identified a resource limit or measured a repeatable benefit. More CPU won’t fix a lock-heavy query; more RAM won’t help a slow third-party API. If you do need a different machine, compare CPU generation, memory, disk type, bandwidth, and provider limits—not just the advertised core count.
For general VPS hosting, PowerVPS is one option worth comparing. For GPU-heavy work such as inference or image processing, Vast.ai is a GPU-rental option; a GPU is usually not the right upgrade for a conventional web server. The Server Rental Guide is also a useful resource when comparing rental options and requirements.
Summary
Profile the request before tuning the server. Measure client-side timings, inspect the VPS during the slowdown, and use access logs to find whether time is spent in the web server or upstream application. Then trace the slow dependency, make one change, and repeat the same test. The best fix is the one supported by measurements—not the one that changes the most settings.
Top comments (0)