This is an example of templating responses for ngx.exit(494) calls from the “legalban” Lua module.

# in `server` context
error_page 494 =451 @legal_ban;
 
location @legal_ban {
    # Prevent internal redirection cycle.
    access_by_lua_block {
        return
    }
    root html;
    try_files /assets/legal/region_block.html =451;
    charset utf-8;
    add_header Cache-Control "no-store" always;
}
 
location /assets/legal {
    # Always allow static assets to be accessed.
    access_by_lua_block {
        return
    }
    root html;
    charset utf-8;
    expires 30d;
}
 
# Restrict access by client IP
access_by_lua_block {
    require('legalban').access(ngx.var.remote_addr) # which calls ngx.exit(494) if IP is banned.
}

First, we defined a custom response code 494 for the ban and its internal redirect to @legal_ban. With this, we can return different pages for the same response code 451. If you only have one predefined response per code to send, we can use it directly and skip the response code override in 494 =451.

Then, we define the named location @legal_ban that returns a static HTML file from $prefix/html. If the response code is not in the add_header-specific list, you need always to make that directive work. *_by_lua_block { return } is only needed if there is a cycle of internal redirection, which is the case in our example.

Third, we defined the assets our error page needs, for example .js files and images. These should be referenced by absolute path in our HTML file, and should be always accessible. Therefore, we disable access control with access_by_lua_block { return } and uses a regular location. We also set expires 30d; to allow clients to cache them and reduce server load.

If possible, make the HTML file standalone or depend on resources from third-party domains only to remove the third part altogether. This would significantly simplify our configuration.

Finally, we put access_by_lua_block { ... } here to execute our “legalban” module on every request, and reject those that came from regions we cannot serve for legal reasons.