---
title: "Deny clients listed in a file"
language: "en"
documentation_version: "3.x"
channel: "development"
release_context: null
source_revision: "10297d1e28653321d6996609fc53718d3b7c8278"
working_tree_clean: true
source_path: "docs/guides/access-control.md"
source_sha256: "f40e287a9070760d6cdcf65d35705eb6c9895a36c83aae3bf42ca15f53b1554e"
source_at_revision: true
source_url: "https://github.com/matsumotory/ngx_mruby/blob/10297d1e28653321d6996609fc53718d3b7c8278/docs/guides/access-control.md"
html_url: "https://ngx.mruby.org/guides/access-control/"
markdown_url: "https://ngx.mruby.org/markdown/guides/access-control.md"
last_updated: "2026-10-29"
---

# Deny clients listed in a file

An access handler runs in nginx's access phase, before the content of the
location. A Ruby script there can end the request with a status, for
example 403, or let it go on by returning without a status
([`mruby_access_handler`](/reference/directives/#mruby_access_handler-and-mruby_access_handler_code)).

## The sample

The access handler reads the file that the variable `$deny_list` names and
answers 403 when the client's address is one of its lines. The location
`/listed/` names a list that holds `127.0.0.1`, the address of the test
client, so the request gets 403; `error_page 403 @denied` gives that 403
its body. The location `/open/` names a list without that address.

```nginx title="test/conf/conf.d/74-guide-access.conf"
server {
    listen 18162;
    default_type text/plain;
    error_page 403 @denied;

    # The list of this location holds 127.0.0.1, the address of the case.
    location /listed/ {
        set $deny_list build/nginx/html/guide_access_listed.txt;
        mruby_access_handler build/nginx/html/guide_access.rb;
        mruby_content_handler_code 'Nginx.rputs "welcome\n"';
    }

    # The list of this location does not hold it.
    location /open/ {
        set $deny_list build/nginx/html/guide_access_open.txt;
        mruby_access_handler build/nginx/html/guide_access.rb;
        mruby_content_handler_code 'Nginx.rputs "welcome\n"';
    }

    location @denied {
        return 403 "your address is on the deny list\n";
    }
}
```

```ruby title="test/html/guide_access.rb"
r = Nginx::Request.new
path = r.var.deny_list
listed = File.exist?(path) && File.read(path).split("\n").include?(r.var.remote_addr)
Nginx.return Nginx::HTTP_FORBIDDEN if listed
```

```http title="test/html/guide_access.request: the request that the case sends"
GET /listed/report HTTP/1.1
Host: 127.0.0.1:18162
Connection: close
```

```http title="test/html/guide_access.response: the response that the case compares"
HTTP/1.1 403 Forbidden
Content-Type: text/plain

your address is on the deny list
```

<p class="caption sample-caption">On every CI run, the case <code>test/t/cases/guides.rb</code> sends this request and compares the status line, the headers shown on this page and the whole body. The five files last changed on <span class="nowrap">2026-10-29</span> in commit <code class="nowrap">32219e53</code>.</p>

The case also sends `GET /open/report` and expects 200 with the body
`welcome`.

## What to keep in mind

- The script reads the list file for every request, which blocks the
  worker while the disk answers. To read it once per worker, load the list
  in `mruby_init_worker_code` into a constant, and reload nginx after a
  change of the file ([What a worker keeps between requests](/concepts/lifetimes/#what-a-worker-keeps-between-requests)).
- `r.var.remote_addr` is the address of the TCP peer. Behind a load
  balancer, that is the load balancer, unless nginx's realip module sets
  it from a header that the load balancer sends.
- `Nginx.return Nginx::HTTP_FORBIDDEN` ends the request with 403. A script
  that returns without `Nginx.return` lets nginx go on with the next
  handler of the access phase and then the content.
