How to Log HTTP Response Body in Nginx Access Log for Debugging


3 views

When debugging API responses or troubleshooting web applications, developers often need to inspect the actual response body sent by the server. While Nginx's access log is excellent for logging request details, it doesn't natively include response body content.

The default Nginx access_log directive captures:

- Client IP address
- Request time
- HTTP method
- Request URI
- Status code
- Response size
- Referrer
- User agent

But crucially, it omits the actual response content, which can be critical for debugging JSON APIs, error messages, or custom headers.

The most effective approach is to use Nginx's Lua module (ngx_http_lua_module) to capture and log response bodies:

http {
    lua_package_path "/path/to/lua/scripts/?.lua;;";
    
    log_format response_body '$remote_addr - $remote_user [$time_local] '
                             '"$request" $status $body_bytes_sent '
                             '"$http_referer" "$http_user_agent" "$resp_body"';
    
    server {
        location / {
            access_log /var/log/nginx/access-with-body.log response_body;
            
            # Capture response body
            set $resp_body "";
            body_filter_by_lua '
                local resp_body = string.sub(ngx.arg[1], 1, 1000)
                ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
                if ngx.arg[2] then
                    ngx.var.resp_body = ngx.ctx.buffered
                end
            ';
        }
    }
}

1. Performance Impact: Logging response bodies significantly increases I/O operations

2. Security: Never use this in production for sensitive data

3. Size Limits: The example above limits to 1000 characters to prevent log bloating

For production debugging, consider creating a dedicated logging endpoint:

location /debug-logger {
    internal;
    content_by_lua '
        local cjson = require "cjson"
        ngx.req.read_body()
        local args = ngx.req.get_post_args()
        ngx.log(ngx.INFO, "Response: ", cjson.encode(args))
        ngx.exit(204)
    ';
}

location /api {
    proxy_pass http://backend;
    post_action /debug-logger;
}

After implementing either solution, verify with:

tail -f /var/log/nginx/access-with-body.log
curl http://localhost/api/test

Remember to remove response body logging before deploying to production environments, as it may expose sensitive information and impact performance.


When debugging web applications, developers often need to inspect the actual response body being served by Nginx. While the access logs naturally capture request data and response headers, logging the response body requires special configuration.

Nginx doesn't provide built-in variables for response body logging, but we can implement workarounds:


# Option 1: Use Lua module (requires ngx_http_lua_module)
location /debug {
    log_subrequest on;
    access_log /var/log/nginx/response.log custom_format;
    
    content_by_lua_block {
        ngx.log(ngx.INFO, "Response: ", ngx.arg[1])
        ngx.say("Your response data")
    }
}

For static content, you can inject logging via sub_filter:


server {
    sub_filter '' '<!-- LOGGED_RESPONSE: $response_body --></body>';
    sub_filter_once off;
    
    log_format custom_log '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         'Response: $response_body';
}
  • Performance impact of logging large response bodies
  • Security implications of logging sensitive data
  • Disk space requirements
  • Need for log rotation policies

For most development purposes, using a custom logging endpoint with Lua provides the best balance:


http {
    lua_shared_dict response_log 10m;
    
    server {
        location /log-response {
            internal;
            content_by_lua_block {
                local resp_body = ngx.shared.response_log:get(ngx.var.request_id)
                ngx.log(ngx.INFO, "Response Body: ", resp_body)
            }
        }
        
        location / {
            proxy_pass http://backend;
            header_filter_by_lua_block {
                ngx.shared.response_log:set(ngx.var.request_id, ngx.arg[1])
            }
            log_subrequest on;
            access_log /path/to/log.log combined;
        }
    }
}