Nginx ACL¶
Overview¶
The Nginx ACL plugin implements an F5-style access control list system for nginx. It uses the njs (nginx JavaScript module) to evaluate rules at request time with near-zero latency.
Key concepts:
- Ruleset (= policy) - a named collection of rules with a single default action
- Rule - one or more conditions combined with AND logic
- Match Mode - how rules within a ruleset are combined (
any= OR,all= AND) - Condition - a single test (IP match, header match, URI match, etc.)
- IP Group - a named set of IP addresses/CIDRs that can be referenced in conditions
- Public vs Private - public rulesets are shared across all websites; private rulesets belong to one website
Plugin Required
Nginx ACL requires the nginx-acl plugin to be enabled. All endpoints below require superuser except the per-website assignment endpoints (which require websites_administration).
Model¶
Ruleset (policy)
├── action: { type: "deny" | "allow" | "redirect", value: "..." }
├── match_mode: "any" | "all"
└── Rules[]
├── enabled: true
└── Conditions[] (AND-combined within a rule)
├── type: "ip" | "header" | "uri" | "method" | "country" | "ip_group"
├── operator: "eq" | "ne" | "contains" | "startswith" | "regex" | "in"
└── value: "..."
Match Mode Semantics¶
match_mode |
Behaviour |
|---|---|
any (default) |
Action fires if any rule matches (OR logic across rules) |
all |
Action fires only if every rule matches (AND logic across rules) |
Conditions within a single rule are always AND-combined regardless of match_mode.
njs Module¶
Before using Nginx ACL, install the njs module:
Rulesets¶
List Rulesets¶
Response:
{
"data": {
"private": [
{
"id": "rs_1718012345678",
"name": "Block Bad Bots",
"enabled": true,
"is_public": false,
"match_mode": "any",
"action_type": "deny",
"action_value": "",
"rule_count": 3
}
],
"public": []
}
}
Create a Ruleset¶
POST /api/nginx-acl/rulesets
{
"name": "Block Bad Bots",
"is_public": false,
"match_mode": "any",
"action_type": "deny",
"action_value": "",
"website_id": 42
}
| Field | Type | Description |
|---|---|---|
name |
string | Human-readable name |
is_public |
bool | If true, shared across all websites |
match_mode |
string | "any" (default) or "all" |
action_type |
string | "deny", "allow", or "redirect" |
action_value |
string | Redirect URL when action_type is "redirect" |
website_id |
int | Website the ruleset belongs to |
Response:
Update a Ruleset¶
All fields are optional (partial update).
Delete a Ruleset¶
Reorder Rulesets¶
Apply a Ruleset¶
Writes the ruleset configuration to the njs config file and reloads nginx:
Warning
Changes to rulesets are not live until apply is called.
Rules¶
List Rules¶
Response:
{
"data": [
{
"id": "rule_1718012345678",
"name": "Block known bad bots",
"enabled": true,
"sort_order": 0,
"conditions": [
{
"type": "header",
"field": "User-Agent",
"operator": "contains",
"value": "scrapy"
}
],
"action": { "type": "deny" }
}
]
}
Create a Rule¶
POST /api/nginx-acl/rulesets/:id/rules
{
"website_id": 42,
"name": "Block scrapy bot",
"enabled": true
}
Update a Rule¶
Delete a Rule¶
Reorder Rules¶
POST /api/nginx-acl/rulesets/:id/rules/reorder
{
"website_id": 42,
"order": ["rule_abc", "rule_def"]
}
Conditions¶
Add a Condition to a Rule¶
POST /api/nginx-acl/rules/:id/conditions
{
"website_id": 42,
"type": "ip",
"operator": "in",
"value": "198.51.100.0/24"
}
| Condition Type | field |
Available Operators |
|---|---|---|
ip |
- | eq, ne, in (CIDR), ip_group |
ip_group |
- | in (references IP group by name) |
header |
Header name (e.g. User-Agent) |
eq, ne, contains, startswith, regex |
uri |
- | eq, ne, contains, startswith, regex |
method |
- | eq, ne |
country |
- | eq, ne |
Delete a Condition¶
IP Groups¶
IP groups are named sets of IPs/CIDRs that can be referenced in conditions. They are stored globally (shared across all rulesets).
List IP Groups¶
Response:
Create an IP Group¶
Delete an IP Group¶
List IP Entries in a Group¶
Add IP Entry¶
Delete IP Entry¶
Per-Website ACL Assignment¶
Public rulesets can be assigned to specific websites without copying them. This allows central management of shared policies.
# List rulesets assigned to a website
GET /api/webapps/:id/acl
# List public rulesets available to assign
GET /api/webapps/:id/acl/available
# Assign a public ruleset to a website
POST /api/webapps/:id/acl/assign
{ "ruleset_id": "rs_1718012345678" }
# Unassign a ruleset from a website
DELETE /api/webapps/:id/acl/:ruleset_id
Data Storage¶
ACL config is stored per website in /etc/starkone/users_data/<username>/nginx_acl.json. Public rulesets are stored in /etc/starkone/nginx_acl_public.json.
{
"rulesets": [
{
"id": "rs_abc",
"name": "Block Bad Bots",
"enabled": true,
"is_public": false,
"match_mode": "any",
"action": { "type": "deny", "value": "" },
"rules": [
{
"id": "rule_def",
"name": "Scrapy bot",
"enabled": true,
"sort_order": 0,
"conditions": [
{
"type": "header",
"field": "User-Agent",
"operator": "contains",
"value": "scrapy"
}
],
"action": { "type": "deny" }
}
]
}
],
"ip_groups": {
"office_ips": ["203.0.113.0/24"]
}
}
Example: Block a Country¶
# 1. Create a ruleset
POST /api/nginx-acl/rulesets
{
"name": "Block CN",
"is_public": false,
"match_mode": "any",
"action_type": "deny",
"website_id": 42
}
# 2. Create a rule
POST /api/nginx-acl/rulesets/rs_xxx/rules
{ "website_id": 42, "name": "China IPs", "enabled": true }
# 3. Add condition
POST /api/nginx-acl/rules/rule_yyy/conditions
{
"website_id": 42,
"type": "country",
"operator": "eq",
"value": "CN"
}
# 4. Apply
POST /api/nginx-acl/rulesets/rs_xxx/apply?website_id=42
Example: Allow Only Office IPs¶
# 1. Create IP group
POST /api/nginx-acl/ip-groups
{ "name": "office" }
POST /api/nginx-acl/ip-groups/office/entries
{ "ip": "203.0.113.0/24" }
# 2. Create ruleset with action "allow" and match_mode "all"
POST /api/nginx-acl/rulesets
{
"name": "Allow Office Only",
"is_public": false,
"match_mode": "all",
"action_type": "allow",
"website_id": 42
}
# 3. Create rule with ip_group condition
POST /api/nginx-acl/rulesets/rs_xxx/rules
{ "website_id": 42, "name": "Office IPs", "enabled": true }
POST /api/nginx-acl/rules/rule_yyy/conditions
{
"website_id": 42,
"type": "ip_group",
"operator": "in",
"value": "office"
}
# 4. Apply
POST /api/nginx-acl/rulesets/rs_xxx/apply?website_id=42