# How web proxies work

> How a Coritan web proxy serves a hostname, and how its origin, certificate, WAF, redirects and error page fit together.

Source: https://www.coritan.com/docs/proxies/web-proxies/

In the dashboard:

- /dashboard/proxies/web/…/overview: https://www.coritan.com/dashboard/proxies/web

A *web proxy* puts one hostname, such as `www.example.com`, behind Coritan's edge. The edge answers HTTP and HTTPS for the name with a certificate issued for it, checks requests with the web application firewall (WAF), applies your redirects and forwards each remaining request to your *origin*: the server that runs the site. The origin can be one of your servers hosted here or any public address. The dashboard also calls a web proxy a *route*, and gives each one a number such as `Route #31`.

## How the edge handles a request

The edge handles each request for the name in this order. The first step that answers ends the request.

1. On plain HTTP, the edge answers certificate validation requests under `/.well-known/acme-challenge/` for certificates it is issuing itself.
2. When the name has no web proxy, the edge answers `404` with a page that reads `This domain is not assigned to any active service on our network.`
3. A [whole-domain redirect](/docs/proxies/web-proxies/redirect-rules/#redirect-the-whole-domain), when set, answers every request. The origin is never contacted.
4. With **Force HTTPS** on, a plain HTTP request gets a `301` redirect to the same address over HTTPS.
5. [Redirect rules](/docs/proxies/web-proxies/redirect-rules/) answer requests whose path matches one, highest priority first.
6. A client that sends too many requests gets `429` with `{"error":"Too many requests"}`.
7. With **WebSockets** on, a request that opens a WebSocket connection goes straight to the origin.
8. The edge answers from its cache when it holds a copy of a static file ([Caching](#caching)).
9. With the **Web application firewall** on, the WAF checks the request ([The web application firewall](#the-web-application-firewall)).
10. A request body larger than the web proxy's limit gets `413` with `{"error":"Request body too large"}`.
11. The edge forwards the request to an origin and passes the origin's answer back to the visitor.

## Origins

The origin is an IP address or a hostname, with a port. It must be reachable from the internet: the API refuses an origin on a private or reserved network, such as `10.0.0.10` or `localhost`, and a hostname that does not resolve in public DNS.

**Connect to the origin over TLS** makes the edge use HTTPS on the way to the origin. The origin must then present a valid certificate for the origin host from a public certificate authority. A self-signed certificate, an expired one or one for another name fails, and visitors get an error page that names `Origin TLS failed`. When the origin host is an IP address, the certificate must list that address.

A web proxy can also have *additional origins*. The edge spreads requests across the origin host and every additional origin, and favours the one with the fewest requests in progress. It does not check the origins' health in advance, so every origin must be able to answer every request.

- When the edge cannot connect to an origin, it tries the next one. It tries a `GET`, `HEAD` or `OPTIONS` request twice on each origin.
- An origin that keeps failing is skipped for a short time, then tried again. When every origin is skipped, visitors get an error page that names `All origins marked down`.

### Origin protocols

The **Origin protocol** sets how the edge talks to the origin. It depends on **Connect to the origin over TLS** as this table shows.

| Origin protocol | API value | With TLS to the origin | Without TLS |
|---|---|---|---|
| **HTTP/1.1** | `h1` | HTTP/1.1 over TLS | HTTP/1.1 |
| **HTTP/2 over TLS** | `h2` | HTTP/2 over TLS | HTTP/2 without TLS |
| **HTTP/2 cleartext** | `h2c` | HTTP/2 without TLS | HTTP/2 without TLS |
| **Negotiate** | `auto` | HTTP/2 or HTTP/1.1, whichever the origin offers | HTTP/1.1 |

The origin protocol does not change how visitors connect to the edge.

## What your origin receives

- The visitor's method, path and headers, including `Host`, so the origin sees the web proxy's name.
- `X-Forwarded-For` and `X-Real-IP` with the visitor's IP address, and `X-Forwarded-Proto` with `https` or `http`. The edge removes any `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` or `Forwarded` header the visitor sent.
- Your custom request headers, on every request.
- With **PROXY protocol to origin** on, a PROXY protocol version 2 header at the start of each connection, carrying the visitor's address. The origin must expect it, or every request fails.

The path changes when you set a prefix. The edge removes **Strip path prefix** from the start of the path, then adds **Prepend origin path** in front of what is left. With `/api` stripped and `/v2` prepended, a request for `/api/users?page=2` reaches the origin as `/v2/users?page=2`, and a request for `/about` as `/v2/about`. When the origin answers with a `Location` or `Content-Location` header, the edge maps its path back, so visitors get a path they can request.

## Certificates

The edge serves HTTPS for the name with a certificate issued for it ([How SSL/TLS certificates work](/docs/websites/ssl/)). A new web proxy requests one while **Request a certificate automatically** is on. Until the certificate is `active`, the edge answers HTTPS for the name with a certificate for another name, and browsers warn visitors.

The certificate can only be issued once the name reaches the edge, or when Coritan hosts its DNS zone ([Web proxies and DNS records](#web-proxies-and-dns-records)). [Request or renew the certificate](/docs/proxies/web-proxies/change-the-origin/#request-or-renew-the-certificate) covers the web proxy's **Certificate** card.

## The web application firewall

The WAF checks each request against a managed rule set before the request reaches your origin. The rules look for SQL injection, cross-site scripting, command injection, path traversal, server-side request forgery, XML external entities, header injection and vulnerability scanners. They read the path, the query string, the method, the headers and the first 64 KB of the request body.

Each rule that matches adds to the request's score.

- A request with a high score gets a `403` page that reads `The web application firewall blocked your request to protect this website from traffic that looks malicious.`
- A request with a lower score that still looks suspicious gets a page titled `Verify you are human`, which asks the visitor to confirm they are not a bot. A browser that passes is not asked again on that web proxy for 20 minutes, as long as its IP address stays the same. Scripts and API clients cannot pass the check.

You cannot see or change the rules, and you cannot exempt a path. The **Web application firewall** switch turns the WAF on or off for the whole web proxy ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/)). The WAF does not check a request that opens a WebSocket connection while **WebSockets** is on, or a file the edge answers from its cache.

## Redirects

A web proxy can redirect in two ways ([Redirect requests with rules](/docs/proxies/web-proxies/redirect-rules/)):

- *Redirect rules* send matching paths elsewhere, and the other requests still reach the origin.
- A *whole-domain redirect* sends every request for the name to another address, and the origin is never contacted.

## Caching

The edge keeps copies of static files and answers later requests for them without asking the origin. It caches an answer only when all of these hold:

- The request is a `GET` or `HEAD` with no `Authorization` header, and the origin answered `200`.
- The answer is an image, a font, an audio file or a video, going by its `Content-Type`. An answer with no type, or with `application/octet-stream`, counts when its path ends in a media extension such as `.png`, `.woff2` or `.mp4`.
- The answer has no `Set-Cookie` header, and its `Cache-Control` has neither `no-store` nor `private`, nor a `max-age` of `0`.
- The answer states a `Content-Length` of 10 MB or less.
- The path does not start with `/api/`, `/ws/`, `/admin`, `/docs`, `/openapi` or `/health`.

The edge never caches HTML pages. It caches scripts, styles and JSON only under a path that starts with `/assets/v/` and a version, such as `/assets/v/3f9a1c/app.js`, and only when the origin marks them `immutable`.

A cached copy stays for up to an hour, whatever lifetime the origin asks for. Answers carry `X-Cache: HIT` when they come from the cache and `X-Cache: MISS` when they come from the origin. You cannot clear the cache, so give a changed file a new URL when visitors must get it at once.

## Error pages and development mode

When the edge cannot reach the origin, it answers with its own error page: `502` when the connection fails and `504` when the origin does not answer within the origin timeout. The page carries a reference called the *ray ID*, which also travels in the `Coritan-Ray` response header, and the `Coritan-Error` header names the failure.

- [Customise the error page](/docs/proxies/web-proxies/custom-error-page/) changes the page's words, links and look.
- Development mode adds the origin's address and the exact failure while you debug ([Use development mode while you debug](/docs/proxies/web-proxies/settings/#use-development-mode-while-you-debug)).
- [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) explains each failure.

## Web proxies and DNS records

A web proxy only receives traffic once its name resolves to Coritan's edge.

- When Coritan hosts the zone, turn **Proxied** on for the name's `A` or `AAAA` record, and Coritan's nameservers answer with the edge's addresses instead of the record's own ([Proxied records](/docs/websites/dns/#proxied-records)). When you create a web proxy for a name that has no `A`, `AAAA` or `CNAME` record yet, Coritan adds a proxied `A` or `AAAA` record for it, with the origin's address and the comment `Created for the proxy route`.
- When another provider hosts the zone, set the name's `A` record at that provider to the edge address. The **SSL** tab shows the address when you choose **HTTP validation** ([Issue a certificate with HTTP validation](/docs/websites/ssl/issue-a-certificate/#issue-a-certificate-with-http-validation)).

A proxied record works without a web proxy of your own. When you turn **Proxied** on for a name that has no web proxy, Coritan creates one that forwards to the record's address on port 80 over plain HTTP, with the WAF on, and requests a certificate for it. That web proxy is not on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list and you cannot change it. To choose its port, protocol or error page, create a web proxy of your own for the name instead ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)).

A proxied record also acts on a web proxy you created for its name:

- Changing the record's **Content** changes the web proxy's origin host to the new value.
- Turning **Proxied** off, deleting the record or deleting the zone deletes the web proxy, with its settings, redirect rules and error page.

Deleting a web proxy leaves its DNS records as they are. A proxied record for the name then sends visitors to the edge, which answers `404` until the name has a web proxy again.

## The Overview tab

Open a web proxy from [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) to see its page. The header shows the name, its status, its certificate state, such as `TLS active` or `No certificate`, and where it forwards to. The **Overview** tab has three cards.

**Route**
: The **Domain**, the **Origins** with their ports and whether each uses TLS, the **Origin protocol**, how the **Path** changes, the names of any **Custom headers**, whether **PROXY protocol** is sent, the **Limits** and when the web proxy was **Created**. **Edit origin** opens the **Origin** tab ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/)).

**Certificate**
: The certificate the edge serves for the name: **Issued by**, **Expires**, **Covers** and **Renewed**. It also shows how many certificates the registered domain can still get this week ([Request or renew the certificate](/docs/proxies/web-proxies/change-the-origin/#request-or-renew-the-certificate)).

**Switches**
: **Web application firewall**, **Force HTTPS**, **WebSockets** and **Development mode**. Each switch saves as soon as you change it ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/)).

## Limits

| Limit | Value |
|---|---|
| Web proxies for one name | One on the whole platform |
| Request body | 1–100 MB, 100 MB by default |
| Origin timeout | 1–120 seconds, 60 by default |
| Request body the WAF reads | The first 64 KB |
| Largest file the edge caches | 10 MB |
| Time a copy stays in the cache | Up to an hour |
| Development mode | Up to 24 hours, or until you turn it off |

## Read web proxies with the API

List your web proxies, newest first, with `GET /api/v1/proxy/routes`. It takes `page`, from `1`, and `per_page`, from `1` to `200` with `50` by default:

```bash
curl "https://api.coritan.com/api/v1/proxy/routes?page=1&per_page=50" \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

```json
{
  "routes": [
    {
      "id": 31,
      "domain": "www.example.com",
      "upstream_host": "203.0.113.10",
      "upstream_port": 8080,
      "upstream_ssl": false,
      "status": "active",
      "force_https": true,
      "websocket_enabled": true,
      "waf_enabled": true,
      "ssl_mode": "full",
      "redirect_to": null,
      "strip_path_prefix": null,
      "upstream_path_prefix": null,
      "upstream_backends": null,
      "development_mode": false,
      "development_mode_expires_at": null,
      "development_mode_active": false,
      "development_mode_seconds_left": null,
      "certificate": {
        "id": 12,
        "domain": "www.example.com",
        "status": "active",
        "issuer": "letsencrypt",
        "issued_at": "2026-09-01T10:02:11",
        "expires_at": "2026-11-30T10:02:10",
        "wildcard": false
      },
      "created_at": "2026-09-01 10:00:04"
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 50
}
```

`certificate` is the certificate the edge serves for the name, or `null`, and its `status` is the certificate's state ([Certificate statuses](/docs/websites/ssl/#certificate-statuses)). Read it rather than `ssl_mode`, which records the last request: `off` when none was made or the certificate was revoked, `pending` after a request, and `full` when the web proxy took over a certificate that already covered the name. `wildcard` is `true` when a wildcard certificate for the parent name covers it. `development_mode_active` says whether the edge still shows diagnostics, because development mode can have expired while `development_mode` is still `true`.

Read one web proxy with `GET /api/v1/proxy/routes/{route_id}`:

```bash
curl https://api.coritan.com/api/v1/proxy/routes/31 \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

The answer holds every stored setting, including `upstream_protocol`, `send_proxy_protocol`, `grpc_enabled`, `max_body_size_mb`, `timeout_seconds`, `custom_headers`, the `redirect_*` fields and `error_page`. Booleans other than the `development_mode*` fields come back as `0` or `1`, and `custom_headers` and `upstream_backends` come back as JSON text. A web proxy that is not on your account answers `404` with `Route not found`. [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api) describes each field.

## API

- `GET /api/v1/proxy/routes`: List proxy routes owned by the current user (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes)
- `GET /api/v1/proxy/routes/{route_id}`: Get details of a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes-route-id)
