Skip to main content

Understanding, measuring and logging response time with Nginx

There are plenty of performance metrics available when it comes to WordPress and other web applications. Some are quite arbitrary numbers or grades, like Google PageSpeed and WebPageTest.org scores. Others are very real, like the total size of a page, or how much time it takes from a sent request to the first byte of the response.

In this module we focus primarily on response time. This metric is very measurable, has little variance from outside factors such as latency, distance and network bandwidth. It is also heavily influenced by our server configuration, WordPress plugins and themes, and other things well within our control.

Response time

When an HTTP request reaches Nginx, it can take several paths. It might be a static file served from disk (or memory), a FastCGI request served from cache, or it could be passed to our PHP-FPM server for further processing, or even proxied to a remote HTTP service.

The time it takes from Nginx receiving the first byte of the request to Nginx sending the last byte of the response is captured in the $request_time variable. This includes any time spent waiting for upstream services.

When it comes to WordPress, however, we're usually more interested in the time spent waiting for PHP specifically. That's where most of the performance bottlenecks are, and that's where most optimization work should be targeted.

The time spent waiting for an upstream (PHP-FPM in our case) is captured in the $upstream_response_time variable in Nginx.

Logging

We can log both the request time and upstream response time in Nginx using the access_log and log_format directives. Let's update our JSON log file format to include both in /config/nginx/nginx.conf:

http {
    log_format json escape=json
        '{'
            '"time_local":"$time_local",'
            '"remote_addr":"$remote_addr",'
            '"cloudflare_ray_id":"$http_cf_ray",'
            '"cloudflare_country":"$http_cf_ipcountry",'
            '"request_time":"$request_time",'
            '"upstream_response_time":"$upstream_response_time",'
            '"request":"$request",'
            '"status":"$status"'
        '}';

    # ...
}

Make sure your site configuration is using this log format:

server {
    access_log /sites/uncached.org/logs/access.log;
    access_log /sites/uncached.org/logs/access.json.log json;
    error_log /sites/uncached.org/logs/error.log warn;

    # ...
}

This will write an access.json.log alongside the original access.log. I usually keep the original access log for compatibility reasons. There are plenty of online tools built decades ago that support this specific format.

For all my complex querying needs though, I almost always use the JSON log.

Querying

While you can still use sed, grep and all the other standard Linux tools for searching and manipulating text, when it comes to JSON, jq is king. It's incredibly powerful, very modern, and has quite a large community that's ready to help when your AI fails to deliver.

You can get jq using apt in Ubuntu and other Debian-based distributions. While we're at it, let's also get datamash, a very useful command-line tool for compiling various stats, as you will see shortly.

sudo apt install jq datamash

The following query shows all requests that took longer than 0.3 seconds in the PHP upstream:

jq -r 'select(.upstream_response_time | tonumber? > 0.3)' \
  /sites/uncached.org/logs/access.json.log

Here's one that shows just the upstream response times for the last 1000 requests:

jq -r 'select(.upstream_response_time | tonumber? > 0) | .upstream_response_time' \
  /sites/uncached.org/logs/access.json.log | tail -n1000

Not very useful on its own, but we can combine this with datamash:

jq -r 'select(.upstream_response_time | tonumber? > 0) | .upstream_response_time' \
  /sites/uncached.org/logs/access.json.log | tail -n1000 \
  | datamash --header-out min 1 max 1 mean 1 median 1 perc:95 1 count 1 \
  | column -t

Response time with Nginx, jq and datamash

This gives us the minimum, maximum, averages, and the 95th percentile of the upstream response times in the last 1000 PHP requests. This can be used for reporting, monitoring and alerts. It's also fun to watch during a load test.

This article is for premium members only. One-time payment of $96 unlocks lifetime access to all existing and future content on wpshell.com, and many other perks.