kong.request reads what the client sent, kong.service.request changes what the upstream receives, and kong.response shapes what goes back. Using ngx.* directly bypasses Kong's own bookkeeping.
-- read
local path = kong.request.get_path()
local method = kong.request.get_method()
local token = kong.request.get_header("authorization")
local q = kong.request.get_query_arg("page")
-- change what the upstream gets
kong.service.request.set_header("X-Tenant", "acme")
kong.service.request.clear_header("Cookie")
-- answer without proxying at all
if not token then
return kong.response.exit(401, { message = "token required" },
{ ["Content-Type"] = "application/json" })
end
# kong.response.exit stops the plugin chain and replies immediately - the
# upstream is never called. It is how every auth plugin rejects a request.
#
# kong.request.get_header vs kong.service.request.set_header is the
# distinction people miss: the first reads the CLIENT's request, the second
# edits the request Kong is about to make. Setting a header with
# kong.response.set_header in the access phase does nothing useful.
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-08-25