# Redirects, basic auth and security rules
URL: https://synquic.tech/docs/guides/edge-rules.md

Redirect paths, put a password on a site or a folder, rate-limit clients and block IPs, countries or bots, all enforced at the edge and applied instantly without a redeploy.

import { Step, Steps } from 'fumadocs-ui/components/steps';

**Route rules** are checks the edge (OpenResty on ports 80 and 443) runs on every request before it reaches your
app. Because the edge turns a request away itself, a blocked or rate-limited visitor costs your app nothing,
and you don't have to change or redeploy any code. Saving a rule pushes it to the edge immediately.

A rule can do any mix of:

- **redirect** a path or a pattern to another path or URL (`301`, `302`, `307` or `308`);
- **basic auth**: ask for a username and password;
- **rate limit** each client IP;
- **allow only** some HTTP methods or countries, **deny** IP ranges, **ban** IPs, countries or user agents;
- **hotlink protection**: only serve requests whose `Referer` is one of your sites.

<Callout title="Where this lives">
In a project's menu, **Redirects** shows only the redirect fields and the rules that redirect; **Security**
shows basic auth, rate limits and the IP, country and user-agent fields and the rules that use them. The same
editor, with every field, is the collapsed **Route rules** card at the bottom of the project's **Domains &
Routes** tab. The entries are shown for projects that run on your own servers.
</Callout>

## Add a rule

<Steps>

<Step>

### Open Redirects or Security

Open the project and choose **Redirects** or **Security**. Existing rules are listed first: the path they
cover (**All paths** when there's no prefix), **All hostnames**, and a one-line summary such as
`20/s +40 · ban 2 · auth admin`.

</Step>

<Step>

### Fill in the Add a rule form

Every field is optional; fill in only what you need. **Path prefix (optional)** limits the rule to one part of
the site, for example `/api` or `/admin`. Leave it empty for the whole site. The prefix is a plain text
match, so `/admin` also covers `/administrator` and `/admin-old`.

</Step>

<Step>

### Save

Press **Add rule**. "Rule saved" means the edge already enforces it. Use the **Enabled** / **Disabled**
button on a rule to switch it off and on, and **Remove** to delete it.

</Step>

</Steps>

<Callout title="Rules can't be edited in place" type="info">
The panel lists rules with **Enabled** / **Disabled** and **Remove**. To change a rule, add the new version and
remove the old one. (The API's `PATCH /api/projects/{id}/route-rules/{ruleId}` can update a rule in place.)
</Callout>

## Redirects

Under **Redirect**:

| Field | What to enter |
| --- | --- |
| **Match** | **Path prefix** or **Regex**. |
| **Status** | `301` (permanent, the default), `302`, `307` or `308`. |
| **From** | The path to match: `/old-blog` for a prefix, or a regular expression such as `^/posts/(\d+)$`. |
| **To (URL or path)** | A path starting with `/` or a full `http://` or `https://` URL. |

How they behave:

- **Path prefix** matches on whole path segments: `/old` matches `/old` and `/old/post`, but not `/older`.
  The rest of the path is appended, so `/old-blog` → `/blog` sends `/old-blog/2024/hello` to
  `/blog/2024/hello`.
- **Regex** matches the path (without the query string). Use `$1` to `$9` in **To** for capture groups:
  `^/posts/(\d+)$` → `/blog/$1`.
- The query string is kept, unless **To** already contains a `?`.
- The redirect is answered at the edge before the request reaches your app.

Examples:

| Match | From | To | Result |
| --- | --- | --- | --- |
| Path prefix | `/docs` | `https://docs.example.com` | `/docs/install?v=2` → `https://docs.example.com/install?v=2` |
| Path prefix | `/old-blog` | `/blog` | `/old-blog/hello` → `/blog/hello` |
| Regex | `^/p/(\d+)$` | `/products/$1` | `/p/42` → `/products/42` |

Limits: a rule holds at most 50 redirects (the panel adds one per rule). A prefix redirect whose relative
destination lies inside its own source (for example `/a` → `/a/b`) is refused because it would loop, and so is
a `$1` in a prefix redirect. `303` isn't offered, because it changes the request method.

To send a whole **domain** to another one (for example `www` to the bare domain), use the domain's **Redirect**
setting instead; see [Redirect one domain to another](/docs/guides/custom-domains#redirect-one-domain-to-another).

## Basic auth

Under **Basic auth** on the **Security** page, enter a **Username** and a **Password**. Visitors then get the
browser's sign-in prompt, and requests without the right credentials receive `401`.

- Put `/admin` in **Path prefix** to protect only that part of the site, or leave it empty to protect everything.
- The username can be 1 to 64 characters without `:`; the password up to 128 characters.
- The password is hashed by the API and never shown again ("The password is stored hashed and can't be shown
  again."). To change it, add a new rule and remove the old one.
- Enter both fields or neither: the form says "Enter both a username and a password" otherwise.
- Basic auth also applies to API calls and webhooks sent to the protected paths. Leave those paths out, or
  your integrations will get `401`.

<Callout title="Use HTTPS" type="warn">
Basic auth sends the password with every request, only encoded, not encrypted. Make sure the domain has an
active certificate so the redirect to HTTPS is on (see [HTTPS redirect](/docs/guides/https-certificates#https-redirect)).
</Callout>

## Rate limits and access control

The remaining **Security** fields:

| Field | What it does |
| --- | --- |
| **Requests / sec** and **Burst** | Each client IP may make *Requests / sec* plus *Burst* requests in any one second on the rule's paths. Over that, the edge answers `429` with `Retry-After: 1`. |
| **Allow only methods** | For example `GET, POST`. Other methods are blocked. |
| **Ban IPs / CIDRs** | Single addresses or ranges, for example `203.0.113.7, 10.0.0.0/8`. |
| **Ban countries (ISO)** | Two-letter country codes, for example `RU, CN`. |
| **Allow only countries (ISO)** | Only these countries get through, for example `US, CA, GB`. |
| **Deny CIDRs** | Ranges to refuse, for example `203.0.113.0/24`. |
| **Hotlink: allow referers** | Hostnames allowed to embed or link your files, for example `example.com`. Requests with another `Referer` are blocked; requests with no `Referer` are allowed. |
| **Ban user-agents (substring)** | Blocks clients whose `User-Agent` contains one of these, for example `curl, python-requests`. Separate entries with commas. |
| **Block empty user-agent** | Blocks requests that send no `User-Agent`. |
| **Block response code** | What blocked requests receive: `403` (default), `404`, `429`, `444` (close the connection), `451` or `503`. |

Examples:

- **Slow down brute force on a login page**: Path prefix `/login`, Requests / sec `5`, Burst `5`.
- **Private staging site**: no path prefix, Allow only countries `DE`, plus basic auth.
- **Keep scrapers off an API**: Path prefix `/api`, Ban user-agents `python-requests, scrapy`, Block empty
  user-agent on.

### How rules combine

For each request the edge picks **one** rule for access control: the enabled rule whose **Path prefix** is the
longest match for the request path (a rule without a prefix is the fallback for everything else). That rule's
checks run in this order: allowed methods, denied and allowed ranges, allowed countries, bans, hotlink, rate
limit. The first check that fails blocks the request.

Basic auth and redirects are different: they look at **every** rule whose prefix matches. The most specific
basic auth applies, and the first matching redirect of the most specific rule wins. So a site-wide password
still covers `/api` even if `/api` has its own rate-limit rule.

This means a rule for `/api` **replaces** a site-wide rate limit or ban for requests under `/api`. Repeat the
settings you want to keep in the more specific rule.

### Known limits

- Rules added in the panel apply to **all hostnames** of the project. To scope a rule to one domain, use the
  CLI's `--domain` option or the API. The panel still labels such a rule **All hostnames**.
- Rule path prefixes (for rate limits, access lists, bans and basic auth) match as plain text: `/api` also
  covers `/api-docs`. A trailing slash (`/api/`) keeps a rule inside one folder, but then no longer covers
  `/api` itself. Redirect sources are different: they match whole path segments.
- Both **Ban countries** and **Allow only countries** block visitors whose country can't be determined.
  Countries come from the GeoLite2 database built into the edge; if the project's **Monitoring** tab says
  "Country data unavailable on this edge", either rule blocks everyone.
- Rules need the Synq edge in front of the app. Projects on Synq Cloud don't have them.
- IP-based checks use the visitor's real address only if the edge can see it. Behind Cloudflare's proxy this
  works automatically (the edge trusts Cloudflare's published ranges and reads `CF-Connecting-IP`). Behind
  another proxy or a load balancer, every visitor looks like the proxy and shares one rate-limit bucket.
  `SYNQ_EDGE_TRUSTED_PROXIES` and `SYNQ_EDGE_REAL_IP_HEADER` only apply to an edge installed directly on a
  server; the edge container of a standard (Docker Compose) install has its real-IP settings built in
  and ignores them. See [Real visitor IPs](/docs/guides/cloudflare-tunnel#real-visitor-ips).

## From the CLI

`synq edge rules` manages the same rules. The CLI covers rate limits, bans and access lists; redirects and basic
auth are set in the panel or through the API.

```bash
# 20 requests/second per client IP on the login path, allowing bursts
synq edge rules add -p <projectId> --path /login --rate-limit 20 --burst 40

# Block two countries and one IP, answering 403
synq edge rules add -p <projectId> --ban-country RU,CN --ban-ip 203.0.113.7 --block-status 403

# Only let an office network in (everyone else is denied)
synq edge rules add -p <projectId> --allow-cidr 198.51.100.0/24

# Scope a rule to one domain of the project
synq edge rules add -p <projectId> --domain <domainId> --rate-limit 50

synq edge rules list -p <projectId>
synq edge rules rm <ruleId> -p <projectId>
```

See the [`synq edge rules` reference](/docs/cli/reference/edge/rules) for every option.

<Callout title="A server-wide limit too" type="info">
Each server also has one rate limit for everything it serves, under **Servers → your server → Security**.
Route rules are the finer-grained, per-project tool. See [Servers](/docs/dashboard/servers).
</Callout>

## Related

<Cards>
  <Card title="Custom domains" href="/docs/guides/custom-domains" description="Domain-to-domain redirects and the Domains & Routes tab." />
  <Card title="Custom proxy config" href="/docs/guides/custom-proxy-config" description="Raw OpenResty directives for cases rules don't cover." />
  <Card title="The edge" href="/docs/guides/the-edge" description="What the edge does and how to take over an existing proxy." />
</Cards>
