How to Log Request Start Time in Nginx Access Logs (Alternative to $time_local)


2 views

While configuring Nginx logging, many developers discover that $time_local doesn't actually represent when a request begins processing. As confirmed in multiple technical discussions, this variable logs the timestamp when Nginx finishes writing the response headers - essentially marking the end (or near-end) of request processing.

Request start time logging is crucial for:

  1. Accurate latency measurement between load balancer and backend
  2. Debugging queueing delays in reverse proxy setups
  3. Calculating true upstream response times
  4. Identifying time synchronization issues

While Nginx doesn't provide a direct variable for request start time, we can leverage the $msec variable with a custom log format:

log_format timing '$remote_addr - $remote_user [$msec] '
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent"';

access_log /var/log/nginx/access.log timing;

The [$msec] will show Unix timestamp with milliseconds when the log entry is written, which is very close to request start time for most practical purposes.

For microsecond precision, combine with Lua scripting:

http {
    lua_shared_dict start_time 10m;
    
    init_by_lua_block {
        local start_time = ngx.shared.start_time
        start_time:set("request_start", ngx.now())
    }

    log_format lua_timing '$remote_addr - $remote_user [$time_local] '
                          'start=$lua_start_time_ms '
                          '"$request" $status $body_bytes_sent';

    server {
        location / {
            access_by_lua_block {
                ngx.var.lua_start_time_ms = ngx.now() * 1000
            }
            access_log /var/log/nginx/access.log lua_timing;
            proxy_pass http://backend;
        }
    }
}

When implementing request start time logging:

  • For high-traffic servers, test the performance impact of Lua operations
  • Consider timezone consistency across your logging pipeline
  • Verify clock synchronization between servers using NTP
  • For containerized environments, ensure host and container clocks are synced

When analyzing the logs, you can use tools like awk to calculate actual processing time:

awk '{print $4, $6, $6-$4}' access.log | sort -n -k3

This will show requests sorted by actual processing duration (end time - start time).


While Nginx's $time_local variable records the end time of requests (or close to it), many performance monitoring scenarios require knowing exactly when a request began. This becomes crucial for:

  • Accurate latency calculations
  • Distributed tracing correlation
  • Request sequencing in high-traffic environments

Nginx doesn't provide a built-in variable for request start time, but we can implement solutions using:

# Option 1: Using $msec with log formatting
log_format timing '$remote_addr - $remote_user [$msec] '
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent"';

For more accurate timestamps with microsecond precision:

# Install ngx_http_lua_module
location / {
    access_by_lua_block {
        ngx.var.request_start_time = ngx.now()
    }

    log_format lua_timing '$remote_addr - $remote_user [$request_start_time] '
                          '"$request" $status $body_bytes_sent';
}

While not ideal, this method can approximate start time:

map $time_iso8601 $request_start_approx {
    default "";
    "~^(?\d{4})-(?\d{2})-(?\d{2})T(?\d{2}):(?\d{2}):(?\d{2})" 
    "${year}-${month}-${day} ${hour}:${min}:${sec}";
}

log_format approx_timing '$remote_addr - $remote_user [$request_start_approx] '
                         '"$request" $status $request_time';

When implementing request timing:

  • Lua operations add ~200-500ns overhead per request
  • For high-traffic servers, consider sampling rather than logging every request
  • Disk I/O becomes the bottleneck beyond 10,000 RPS

Here's a complete configuration for a production environment:

http {
    lua_shared_dict timing 10m;
    
    init_by_lua_block {
        local timing = ngx.shared.timing
        timing:set("request_count", 0)
    }

    log_format full_timing '$remote_addr - $remote_user [$time_iso8601] '
                          '[$request_start_time] "$request" $status '
                          '$body_bytes_sent $request_time';

    server {
        listen 80;
        
        set_by_lua $request_start_time '
            local timing = ngx.shared.timing
            local count = timing:incr("request_count", 1)
            return ngx.now()
        ';

        access_log /var/log/nginx/timing.log full_timing;
    }
}