Skip to content

Routing Rules [[sites.locations]]

locations define URL path matching rules and their corresponding handlers, equivalent to Nginx location blocks.

Match Syntax

PrefixTypeExample
= /pathExact match (highest priority)= /favicon.ico
^~ /prefixPrefix match (skip regex)^~ /static/
~ regexRegex match (case-sensitive)~ \.php$
~* regexRegex match (case-insensitive)~* \.(jpg|png)$
/prefixPlain prefix match/api/

Priority: Exact = > Prefix ^~ > Regex ~/~* > Plain prefix.

Handler Types

ValueDescription
staticStatic file serving (default)
fastcgiPHP / FastCGI forwarding
reverse_proxyHTTP reverse proxy
grpcgRPC proxy
websocketWebSocket proxy
plugin:<name>Custom plugin

Full Configuration

toml
[[sites.locations]]
path    = "/api/"
handler = "reverse_proxy"
upstream = "backend"         # References [[sites.upstreams]] name

# ─── Root Override ────────────────────────────────────────────────
root = "/var/www/other"       # Override site-level root

# ─── Direct Return ───────────────────────────────────────────────
return_code = 200             # Return specified status code (no body)
return_url  = "https://new.example.com$request_uri"  # Redirect
return_body = "OK"            # Return text content
return_content_type = "application/json"

# ─── File Lookup ──────────────────────────────────────────────────
try_files = ["$uri", "$uri/", "/index.php?$args"]  # Equivalent to Nginx try_files

# ─── Response Header Control ─────────────────────────────────────
cache_control = "public, max-age=86400"

[[sites.locations.add_headers]]
name  = "X-Frame-Options"
value = "DENY"

[[sites.locations.proxy_set_headers]]
name  = "X-Real-IP"
value = "$remote_addr"

[[sites.locations.proxy_set_headers]]
name  = "X-Forwarded-Proto"
value = "$scheme"

# ─── Cache Rules (by extension) ──────────────────────────────────
[[sites.locations.cache_rules]]
pattern       = "\\.(css|js|woff2?)$"
cache_control = "public, max-age=2592000, immutable"

[[sites.locations.cache_rules]]
pattern       = "\\.(png|jpg|gif|webp|svg|ico)$"
cache_control = "public, max-age=2592000"

# ─── Connection Limits ───────────────────────────────────────────
limit_conn      = 100         # Concurrent connection limit (0 = unlimited)
max_connections = 50          # WebSocket-specific max connections

# ─── Subrequest Auth (auth_request) ──────────────────────────────
auth_request        = "/auth-check"   # Auth subrequest path
auth_failure_status = 401             # Failure response status code

[[sites.locations.auth_request_headers]]
name  = "Authorization"
value = "$http_authorization"

# ─── Content Replacement (sub_filter) ────────────────────────────
[[sites.locations.sub_filter]]
pattern     = "http://old.example.com"
replacement = "https://new.example.com"

# ─── Reverse Proxy Cookie Handling ───────────────────────────────
strip_cookie_secure  = false
proxy_cookie_domain  = "backend.internal example.com"

# ─── Reverse Proxy Redirect Handling ─────────────────────────────
proxy_redirect_from = "http://backend.internal/"
proxy_redirect_to   = "https://example.com/"

# ─── Buffering Control ───────────────────────────────────────────
proxy_buffering = false   # Disable buffering (set false for SSE/streaming)

Supported Variables

The following variables can be used in value, return_url, return_body, etc.:

VariableDescription
$remote_addrClient IP
$hostRequest Host header
$schemeRequest protocol (http/https)
$request_uriFull request path (with query string)
$uriRequest path (without query string)
$argsQuery string
$http_<name>Request header, e.g. $http_authorization

Common Examples

Static Files with Long Cache

toml
[[sites.locations]]
path    = "~* \\.(js|css|png|jpg|gif|ico|woff2?)$"
handler = "static"

[[sites.locations.cache_rules]]
pattern       = ".*"
cache_control = "public, max-age=2592000, immutable"

PHP Full-Site Forwarding

toml
[[sites.locations]]
path      = "~ \\.php$"
handler   = "fastcgi"
try_files = ["$uri", "=404"]

Health Check Endpoint

toml
[[sites.locations]]
path        = "= /health"
handler     = "static"
return_code = 200
return_body = "OK"

Force CORS Headers

toml
[[sites.locations]]
path    = "/api/"
handler = "reverse_proxy"
upstream = "backend"

[[sites.locations.add_headers]]
name  = "Access-Control-Allow-Origin"
value = "*"

[[sites.locations.add_headers]]
name  = "Access-Control-Allow-Methods"
value = "GET, POST, PUT, DELETE, OPTIONS"

IP Access Control (access_rules)

Equivalent to Nginx allow / deny directives. Rules are matched in order against the client IP; the first match determines the result. Supports exact IP, CIDR ranges, and the all wildcard.

Syntax

toml
[[sites.locations]]
path    = "/admin"
handler = "reverse_proxy"
upstream = "backend"

# Only allow internal network access to admin panel
[[sites.locations.access_rules]]
action = "allow"
source = "10.0.0.0/8"

[[sites.locations.access_rules]]
action = "allow"
source = "172.16.0.0/12"

[[sites.locations.access_rules]]
action = "allow"
source = "192.168.0.0/16"

[[sites.locations.access_rules]]
action = "deny"
source = "all"

Rule Reference

FieldDefaultDescription
action(required)allow or deny
source(required)IP address, CIDR range, or all (matches everything)
priority0Priority (0-1024, lower value = higher priority)
  • Rules are sorted by priority ascending then matched top-to-bottom, first match wins
  • Same-priority rules preserve their order in the config file
  • No rules or no match defaults to allow (consistent with Nginx)
  • Denied requests return 403 Forbidden
  • Supports IPv4 and IPv6
  • Rules are pre-compiled to CIDR matchers at startup, zero allocation at runtime

Typical Usage

toml
# Whitelist mode: allow specific IPs, deny the rest
[[sites.locations.access_rules]]
action = "allow"
source = "1.2.3.4"

[[sites.locations.access_rules]]
action = "deny"
source = "all"

# Blacklist mode: deny specific IPs, allow the rest
[[sites.locations.access_rules]]
action = "deny"
source = "5.6.7.8"

[[sites.locations.access_rules]]
action = "allow"
source = "all"

# Priority mode: deny all at priority 10, allow internal at priority 0 (matched first)
[[sites.locations.access_rules]]
action   = "deny"
source   = "all"
priority = 10

[[sites.locations.access_rules]]
action   = "allow"
source   = "10.0.0.0/8"
priority = 0           # Higher priority, internal IPs match this rule first

When real_ip is configured on the site, access control uses the extracted real client IP, not the connection IP.

Rewrite Rules

rewrite rules perform regex matching and replacement on request paths, equivalent to Nginx's rewrite directive. Rules are evaluated in array order; after a match, the flag determines subsequent behavior.

Syntax

toml
[[sites.locations]]
path = "/"
handler = "reverse_proxy"
upstream = "backend"

[[sites.locations.rewrite]]
pattern   = "^/old/(.*)$"     # Regex pattern (supports capture groups)
target    = "/new/$1"          # Replacement target ($1..$9 reference capture groups)
flag      = "last"             # Behavior flag (default: last)
condition = "!-f"              # Optional trigger condition

Capture Group Substitution

PlaceholderDescription
$0Full match
$1 .. $9Capture groups 1 through 9

Behavior Flags

FlagDescription
lastStop processing subsequent rewrite rules, re-match location (default)
breakStop processing subsequent rewrite rules, no re-match
redirectReturn 302 temporary redirect
permanentReturn 301 permanent redirect

Conditions

ConditionDescription
!-fTrigger when file does not exist
!-dTrigger when directory does not exist
-fTrigger when file exists
-dTrigger when directory exists

Examples

toml
[[sites.locations.rewrite]]
pattern   = "^/(.+)$"
target    = "/index.php?$1"
flag      = "last"
condition = "!-f"

301 Permanent Redirect for Legacy Paths

toml
[[sites.locations.rewrite]]
pattern = "^/blog/(.*)$"
target  = "/articles/$1"
flag    = "permanent"

Chained Rules

toml
# Rule 1: Strip .html suffix
[[sites.locations.rewrite]]
pattern = "^(/.*)\\.html$"
target  = "$1"
flag    = "last"

# Rule 2: API version routing
[[sites.locations.rewrite]]
pattern = "^/api/v1/(.*)$"
target  = "/api/current/$1"
flag    = "break"

Released under the Apache License 2.0